package architect_datatable
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceArchitectDatatableRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
archAPI := platformclientv2.NewArchitectApiWithConfig(sdkConfig)
name := d.Get("name").(string)
// Query architect architect_datatable by name. Retry in case search has not yet indexed the architect architect_datatable.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageNum = 1
const pageSize = 100
datatables, resp, getErr := archAPI.GetFlowsDatatables("", pageNum, pageSize, "", "", nil, name)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting architect architect_datatable %s | error: %s", name, getErr), resp))
}
if datatables.Entities == nil || len(*datatables.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No architect architect_datatable found with name %s", name), resp))
}
datatable := (*datatables.Entities)[0]
d.SetId(*datatable.Id)
return nil
})
}
package architect_datatable
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type Datatableproperty struct {
Id *string `json:"$id,omitempty"`
VarType *string `json:"type,omitempty"`
Title *string `json:"title,omitempty"`
Default *interface{} `json:"default,omitempty"`
DisplayOrder *int `json:"displayOrder,omitempty"`
}
// Overriding the SDK Datatable document as it does not allow setting additionalProperties to 'false' as required by the API
type Jsonschemadocument struct {
Schema *string `json:"$schema,omitempty"`
VarType *string `json:"type,omitempty"`
Required *[]string `json:"required,omitempty"`
Properties *map[string]Datatableproperty `json:"properties,omitempty"`
AdditionalProperties *interface{} `json:"additionalProperties,omitempty"`
}
type Datatable struct {
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Division *platformclientv2.Writabledivision `json:"division,omitempty"`
Schema *Jsonschemadocument `json:"schema,omitempty"`
}
func getAllArchitectDatatables(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
archProxy := getArchitectDatatableProxy(clientConfig)
tables, resp, err := archProxy.getAllArchitectDatatable(ctx)
if err != nil {
return resources, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Error encountered while calling getAllArchitectDatattables error: %s", err), resp)
}
for _, table := range *tables {
resources[*table.Id] = &resourceExporter.ResourceMeta{Name: *table.Name}
}
return resources, nil
}
func createArchitectDatatable(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
divisionID := d.Get("division_id").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableProxy(sdkConfig)
log.Printf("Creating architect_datatable %s", name)
datatableSchema, diagErr := buildSdkDatatableSchema(d)
if diagErr != nil {
return diagErr
}
datatable := &Datatable{
Name: &name,
Schema: datatableSchema,
}
// Optional
if divisionID != "" {
datatable.Division = &platformclientv2.Writabledivision{Id: &divisionID}
}
if description != "" {
datatable.Description = &description
}
table, resp, err := archProxy.createArchitectDatatable(ctx, datatable)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create architect_datatable %s error: %s", *datatable.Name, err), resp)
}
d.SetId(*table.Id)
log.Printf("Created architect_datatable %s %s", name, *table.Id)
return readArchitectDatatable(ctx, d, meta)
}
func readArchitectDatatable(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectDatatable(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading architect_datatable %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
datatable, resp, getErr := archProxy.getArchitectDatatable(ctx, d.Id(), "schema")
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read architect_datatable %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read architect_datatable %s | error: %s", d.Id(), getErr), resp))
}
_ = d.Set("name", *datatable.Name)
_ = d.Set("division_id", *datatable.Division.Id)
if datatable.Description != nil {
_ = d.Set("description", *datatable.Description)
} else {
_ = d.Set("description", nil)
}
if datatable.Schema != nil && datatable.Schema.Properties != nil {
_ = d.Set("properties", flattenDatatableProperties(*datatable.Schema.Properties))
} else {
_ = d.Set("properties", nil)
}
log.Printf("Read architect_datatable %s %s", d.Id(), *datatable.Name)
return cc.CheckState(d)
})
}
func updateArchitectDatatable(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := d.Id()
name := d.Get("name").(string)
divisionID := d.Get("division_id").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableProxy(sdkConfig)
log.Printf("Updating architect_datatable %s", name)
datatableSchema, diagErr := buildSdkDatatableSchema(d)
if diagErr != nil {
return diagErr
}
datatable := &Datatable{
Id: &id,
Name: &name,
Schema: datatableSchema,
}
// Optional
if divisionID != "" {
datatable.Division = &platformclientv2.Writabledivision{Id: &divisionID}
}
if description != "" {
datatable.Description = &description
}
_, resp, err := archProxy.updateArchitectDatatable(ctx, datatable)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update architect_datatable %s, error: %s", name, err), resp)
}
log.Printf("Updated architect_datatable %s", name)
return readArchitectDatatable(ctx, d, meta)
}
func deleteArchitectDatatable(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableProxy(sdkConfig)
log.Printf("Deleting architect_datatable %s", name)
resp, err := archProxy.deleteArchitectDatatable(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete architect_datatable %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
//might neeed to add expand with the "" as the expand
_, resp, err := archProxy.getArchitectDatatable(ctx, d.Id(), "")
if err != nil {
if util.IsStatus404(resp) {
// Datatable row deleted
log.Printf("Deleted architect_datatable row %s", name)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting architect_datatable row %s | error: %s", name, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Datatable row %s still exists", name), resp))
})
}
package architect_datatable
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *architectDatatableProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOrUpdateArchitectDatatableFunc func(ctx context.Context, p *architectDatatableProxy, createAction bool, datatable *Datatable) (*Datatable, *platformclientv2.APIResponse, error)
type deleteArchitectDatatableFunc func(ctx context.Context, p *architectDatatableProxy, datatableId string) (*platformclientv2.APIResponse, error)
type getArchitectDatatableFunc func(ctx context.Context, p *architectDatatableProxy, datatableId string, expanded string) (*Datatable, *platformclientv2.APIResponse, error)
type getAllArchitectDatatableFunc func(ctx context.Context, p *architectDatatableProxy) (*[]platformclientv2.Datatable, *platformclientv2.APIResponse, error)
type architectDatatableProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createOrUpdateArchitectDatatableAttr createOrUpdateArchitectDatatableFunc
getArchitectDatatableAttr getArchitectDatatableFunc
getAllArchitectDatatableAttr getAllArchitectDatatableFunc
deleteArchitectDatatableAttr deleteArchitectDatatableFunc
}
func newArchitectDatatableProxy(clientConfig *platformclientv2.Configuration) *architectDatatableProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &architectDatatableProxy{
clientConfig: clientConfig,
architectApi: api,
createOrUpdateArchitectDatatableAttr: createOrUpdateArchitectDatatableFn,
getArchitectDatatableAttr: getArchitectDatatableFn,
getAllArchitectDatatableAttr: getAllArchitectDatatableFn,
deleteArchitectDatatableAttr: deleteArchitectDatatableFn,
}
}
func getArchitectDatatableProxy(clientConfig *platformclientv2.Configuration) *architectDatatableProxy {
if internalProxy == nil {
internalProxy = newArchitectDatatableProxy(clientConfig)
}
return internalProxy
}
func (p *architectDatatableProxy) createArchitectDatatable(ctx context.Context, datatable *Datatable) (*Datatable, *platformclientv2.APIResponse, error) {
return p.createOrUpdateArchitectDatatableAttr(ctx, p, true, datatable)
}
func (p *architectDatatableProxy) updateArchitectDatatable(ctx context.Context, datatable *Datatable) (*Datatable, *platformclientv2.APIResponse, error) {
return p.createOrUpdateArchitectDatatableAttr(ctx, p, false, datatable)
}
func (p *architectDatatableProxy) getArchitectDatatable(ctx context.Context, id string, expanded string) (*Datatable, *platformclientv2.APIResponse, error) {
return p.getArchitectDatatableAttr(ctx, p, id, expanded)
}
func (p *architectDatatableProxy) getAllArchitectDatatable(ctx context.Context) (*[]platformclientv2.Datatable, *platformclientv2.APIResponse, error) {
return p.getAllArchitectDatatableAttr(ctx, p)
}
func (p *architectDatatableProxy) deleteArchitectDatatable(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return p.deleteArchitectDatatableAttr(ctx, p, id)
}
func createOrUpdateArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy, createAction bool, datatable *Datatable) (*Datatable, *platformclientv2.APIResponse, error) {
apiClient := &p.architectApi.Configuration.APIClient
action := http.MethodPost
// create path and map variables
path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables"
if !createAction {
action = http.MethodPut
path += "/" + *datatable.Id
}
headerParams := make(map[string]string)
// add default headers if any
for key := range p.architectApi.Configuration.DefaultHeader {
headerParams[key] = p.architectApi.Configuration.DefaultHeader[key]
}
headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *Datatable
response, err := apiClient.CallAPI(path, action, datatable, headerParams, nil, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
}
return successPayload, response, err
}
func getArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy, datatableId string, expanded string) (*Datatable, *platformclientv2.APIResponse, error) {
apiClient := &p.architectApi.Configuration.APIClient
// create path and map variables
path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables/" + datatableId
headerParams := make(map[string]string)
queryParams := make(map[string]string)
// oauth required
if p.architectApi.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken
}
// add default headers if any
for key := range p.architectApi.Configuration.DefaultHeader {
headerParams[key] = p.architectApi.Configuration.DefaultHeader[key]
}
queryParams["expand"] = apiClient.ParameterToString(expanded, "")
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *Datatable
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal(response.RawBody, &successPayload)
}
return successPayload, response, err
}
func deleteArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy, datatableId string) (*platformclientv2.APIResponse, error) {
return p.architectApi.DeleteFlowsDatatable(datatableId, true)
}
func getAllArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy) (*[]platformclientv2.Datatable, *platformclientv2.APIResponse, error) {
var totalRecords []platformclientv2.Datatable
const pageSize = 100
tables, resp, getErr := p.architectApi.GetFlowsDatatables("", 1, pageSize, "", "", nil, "")
if getErr != nil {
return &totalRecords, resp, getErr
}
if tables.Entities == nil || len(*tables.Entities) == 0 {
return &totalRecords, resp, nil
}
totalRecords = append(totalRecords, *tables.Entities...)
for pageNum := 2; pageNum <= *tables.PageCount; pageNum++ {
tables, resp, getErr := p.architectApi.GetFlowsDatatables("", pageNum, pageSize, "", "", nil, "")
if getErr != nil {
return &totalRecords, resp, getErr
}
if tables.Entities == nil || len(*tables.Entities) == 0 {
break
}
totalRecords = append(totalRecords, *tables.Entities...)
}
return &totalRecords, resp, nil
}
package architect_datatable
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_architect_datatable"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceArchitectDatatable())
regInstance.RegisterDataSource(resourceName, DataSourceArchitectDatatable())
regInstance.RegisterExporter(resourceName, ArchitectDatatableExporter())
}
var (
datatableProperty = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the property.",
Type: schema.TypeString,
Required: true,
},
"type": {
Description: "Type of the property (boolean | string | integer | number).",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"boolean", "string", "integer", "number"}, false),
},
"title": {
Description: "Display title of the property.",
Type: schema.TypeString,
Optional: true,
},
"default": {
Description: "Default value of the property. This is converted to the proper type for non-strings (e.g. set 'true' or 'false' for booleans).",
Type: schema.TypeString,
Optional: true,
},
},
}
)
func ResourceArchitectDatatable() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Architect Datatables",
CreateContext: provider.CreateWithPooledClient(createArchitectDatatable),
ReadContext: provider.ReadWithPooledClient(readArchitectDatatable),
UpdateContext: provider.UpdateWithPooledClient(updateArchitectDatatable),
DeleteContext: provider.DeleteWithPooledClient(deleteArchitectDatatable),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the architect_datatable.",
Type: schema.TypeString,
Required: true,
},
"division_id": {
Description: "The division to which this architect_datatable will belong. If not set, the home division will be used.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Description: "Description of the architect_datatable.",
Type: schema.TypeString,
Optional: true,
},
"properties": {
Description: "Schema properties of the architect_datatable. This must at a minimum contain a string property 'key' that will serve as the row key. Properties cannot be removed from a schema once they have been added",
Type: schema.TypeList,
Required: true,
MinItems: 1,
Elem: datatableProperty,
},
},
}
}
func DataSourceArchitectDatatable() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Architect Datatables. Select an architect architect_datatable by name.",
ReadContext: provider.ReadWithPooledClient(DataSourceArchitectDatatableRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Datatable name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func ArchitectDatatableExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllArchitectDatatables),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
},
}
}
package architect_datatable
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"sort"
"strconv"
"terraform-provider-genesyscloud/genesyscloud/util"
)
func buildSdkDatatableSchema(d *schema.ResourceData) (*Jsonschemadocument, diag.Diagnostics) {
// Hardcoded values the server expects in the JSON schema object
var (
schemaType = "http://json-schema.org/draft-04/schema#"
jsonType = "object"
additionalProperties interface{}
)
additionalProperties = false
properties, err := buildSdkDatatableProperties(d)
if err != nil {
return nil, err
}
return &Jsonschemadocument{
Schema: &schemaType,
VarType: &jsonType,
Required: &[]string{"key"},
Properties: properties,
AdditionalProperties: &additionalProperties,
}, nil
}
func buildSdkDatatableProperties(d *schema.ResourceData) (*map[string]Datatableproperty, diag.Diagnostics) {
const propIdPrefix = "/properties/"
if properties := d.Get("properties").([]interface{}); properties != nil {
sdkProps := map[string]Datatableproperty{}
for i, property := range properties {
propMap := property.(map[string]interface{})
// Name and type are required
propName := propMap["name"].(string)
propType := propMap["type"].(string)
propId := propIdPrefix + propName
orderNum := i
sdkProp := Datatableproperty{
Id: &propId,
DisplayOrder: &orderNum,
VarType: &propType,
}
// Title is optional
if propTitle, ok := propMap["title"]; ok {
title := propTitle.(string)
sdkProp.Title = &title
}
// Default is optional
if propDefault, ok := propMap["default"]; ok {
def := propDefault.(string)
var defaultVal interface{}
if def != "" {
var err error
// Convert default value to the appropriate type
switch propType {
case "boolean":
defaultVal, err = strconv.ParseBool(def)
case "string":
defaultVal = def
case "integer":
defaultVal, err = strconv.Atoi(def)
case "number":
defaultVal, err = strconv.ParseFloat(def, 64)
default:
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Invalid type %s for Datatable property %s", propType, propName), fmt.Errorf("invalid type for Datatable property"))
}
if err != nil {
return nil, diag.FromErr(err)
}
}
if defaultVal != nil {
sdkProp.Default = &defaultVal
}
}
sdkProps[propName] = sdkProp
}
return &sdkProps, nil
}
return nil, nil
}
func flattenDatatableProperties(properties map[string]Datatableproperty) []interface{} {
configProps := []interface{}{}
type kv struct {
Key string
Value Datatableproperty
}
var propList []kv
defaultOrder := 0
for k, v := range properties {
if v.DisplayOrder == nil {
// Set a default so the sort doesn't fail
v.DisplayOrder = &defaultOrder
}
propList = append(propList, kv{k, v})
}
// Sort by display order
sort.SliceStable(propList, func(i, j int) bool {
return *propList[i].Value.DisplayOrder < *propList[j].Value.DisplayOrder
})
for _, propKV := range propList {
propMap := make(map[string]interface{})
propMap["name"] = propKV.Key
if propKV.Value.VarType != nil {
propMap["type"] = *propKV.Value.VarType
}
if propKV.Value.Title != nil {
propMap["title"] = *propKV.Value.Title
}
if propKV.Value.Default != nil {
propMap["default"] = util.InterfaceToString(*propKV.Value.Default)
}
configProps = append(configProps, propMap)
}
return configProps
}
package architect_datatable_row
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// Row IDs structured as {table-id}/{key-value}
func createDatatableRowId(tableId string, keyVal string) string {
return strings.Join([]string{tableId, keyVal}, "/")
}
func splitDatatableRowId(rowId string) (string, string) {
split := strings.SplitN(rowId, "/", 2)
if len(split) == 2 {
return split[0], split[1]
}
return "", ""
}
func buildSdkRowPropertyMap(propertiesJson string, keyStr string) (map[string]interface{}, diag.Diagnostics) {
// Property value must be empty or a JSON object (map)
propMap := map[string]interface{}{}
if propertiesJson != "" {
if err := json.Unmarshal([]byte(propertiesJson), &propMap); err != nil {
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error parsing properties_json value %s", propertiesJson), err)
}
}
// Set the key value
propMap["key"] = keyStr
return propMap, nil
}
func customizeDatatableRowDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
// Defaults must be set on missing properties
if !diff.NewValueKnown("properties_json") {
// properties_json value not yet in final state. Nothing to do.
return nil
}
if !diff.NewValueKnown("datatable_id") {
// datatable_id not yet in final state, but properties_json is marked as known.
// There may be computed defaults to set on properties_json that we do not know yet.
diff.SetNewComputed("properties_json")
return nil
}
tableId := diff.Get("datatable_id").(string)
keyStr := diff.Get("key_value").(string)
id := createDatatableRowId(tableId, keyStr)
propertiesJson := diff.Get("properties_json").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
// Retrieve defaults from the architect_datatable for this row
datatable, getErr := getArchitectDatatableCached(ctx, tableId, sdkConfig)
if getErr != nil {
return fmt.Errorf("Failed to read architect_datatable %s: %s", tableId, getErr)
}
// Parse resource properties into map
configMap := map[string]interface{}{}
if propertiesJson == "" {
propertiesJson = "{}" // empty object by default
}
if err := json.Unmarshal([]byte(propertiesJson), &configMap); err != nil {
return fmt.Errorf("Failure to parse properties_json for %s: %s", id, err)
}
// For each property in the schema, check if a value is set in the config
if datatable.Schema != nil && datatable.Schema.Properties != nil {
for name, prop := range *datatable.Schema.Properties {
if name == "key" {
// Skip setting the key value
continue
}
if _, set := configMap[name]; !set {
// Property in schema not set. Override diff with expected default.
if prop.Default != nil {
configMap[name] = *prop.Default
} else if *prop.VarType == "boolean" {
// Booleans default to false
configMap[name] = false
} else if *prop.VarType == "string" {
// Strings default to empty
configMap[name] = ""
} else if *prop.VarType == "integer" || *prop.VarType == "number" {
// Numbers default to 0
configMap[name] = 0
}
}
}
}
// Marshal back to string and set as the diff value
result, err := json.Marshal(configMap)
if err != nil {
return fmt.Errorf("Failure to marshal properties for %s: %s", id, err)
}
diff.SetNew("properties_json", string(result))
return nil
}
// Prevent getting the architect_datatable schema on every row diff
// by caching the results for the duration of the TF run
var archDatatableCache sync.Map
func getArchitectDatatableCached(ctx context.Context, tableID string, config *platformclientv2.Configuration) (*Datatable, error) {
archProxy := getArchitectDatatableRowProxy(config)
if table, ok := archDatatableCache.Load(tableID); ok {
return table.(*Datatable), nil
}
datatable, _, getErr := archProxy.getArchitectDatatable(ctx, tableID, "schema")
if getErr != nil {
return nil, fmt.Errorf("Failed to read architect_datatable %s: %s", tableID, getErr)
}
archDatatableCache.Store(tableID, datatable)
return datatable, nil
}
package architect_datatable_row
import (
"context"
"encoding/json"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type Datatableproperty struct {
Id *string `json:"$id,omitempty"`
VarType *string `json:"type,omitempty"`
Title *string `json:"title,omitempty"`
Default *interface{} `json:"default,omitempty"`
DisplayOrder *int `json:"displayOrder,omitempty"`
}
// Overriding the SDK Datatable document as it does not allow setting additionalProperties to 'false' as required by the API
type Jsonschemadocument struct {
Schema *string `json:"$schema,omitempty"`
VarType *string `json:"type,omitempty"`
Required *[]string `json:"required,omitempty"`
Properties *map[string]Datatableproperty `json:"properties,omitempty"`
AdditionalProperties *interface{} `json:"additionalProperties,omitempty"`
}
type Datatable struct {
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Division *platformclientv2.Writabledivision `json:"division,omitempty"`
Schema *Jsonschemadocument `json:"schema,omitempty"`
}
func getAllArchitectDatatableRows(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
archProxy := getArchitectDatatableRowProxy(clientConfig)
tables, resp, err := archProxy.getAllArchitectDatatable(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get architect datatables error: %s", err), resp)
}
for _, tableMeta := range *tables {
rows, resp, err := archProxy.getAllArchitectDatatableRows(ctx, *tableMeta.Id)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get architect Datatable Rows error: %s", err), resp)
}
for _, row := range *rows {
if keyVal, ok := row["key"]; ok {
keyStr := keyVal.(string) // Keys must be strings
resources[createDatatableRowId(*tableMeta.Id, keyStr)] = &resourceExporter.ResourceMeta{Name: *tableMeta.Name + "_" + keyStr}
}
}
}
return resources, nil
}
func createArchitectDatatableRow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
tableId := d.Get("datatable_id").(string)
keyStr := d.Get("key_value").(string)
properties := d.Get("properties_json").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableRowProxy(sdkConfig)
rowMap, diagErr := buildSdkRowPropertyMap(properties, keyStr)
if diagErr != nil {
return diagErr
}
rowId := createDatatableRowId(tableId, keyStr)
log.Printf("Creating Datatable Row %s", rowId)
_, resp, err := archProxy.createArchitectDatatableRow(ctx, tableId, &rowMap)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Datatable Row %s error: %s", d.Id(), err), resp)
}
d.SetId(rowId)
log.Printf("Created Datatable Row %s", d.Id())
return readArchitectDatatableRow(ctx, d, meta)
}
func readArchitectDatatableRow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
tableId, keyStr := splitDatatableRowId(d.Id())
if keyStr == "" {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Invalid Row ID %s", d.Id()), fmt.Errorf("keyStr is nil"))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableRowProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectDatatableRow(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Datatable Row %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
row, resp, getErr := archProxy.getArchitectDatatableRow(ctx, tableId, keyStr)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Datatable Row %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Datatable Row %s | error: %s", d.Id(), getErr), resp))
}
d.Set("datatable_id", tableId)
d.Set("key_value", keyStr)
// The key value is exposed through a separate attribute, so it should be removed from the value map
delete(*row, "key")
valueBytes, err := json.Marshal(*row)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("Failed to marshal row map %v: %v", *row, err))
}
d.Set("properties_json", string(valueBytes))
log.Printf("Read Datatable Row %s", d.Id())
return cc.CheckState(d)
})
}
func updateArchitectDatatableRow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
tableId := d.Get("datatable_id").(string)
keyStr := d.Get("key_value").(string)
properties := d.Get("properties_json").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableRowProxy(sdkConfig)
rowMap, diagErr := buildSdkRowPropertyMap(properties, keyStr)
if diagErr != nil {
return diagErr
}
log.Printf("Updating Datatable Row %s", d.Id())
_, resp, err := archProxy.updateArchitectDatatableRow(ctx, tableId, keyStr, &rowMap)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Datatable Row %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated Datatable Row %s", d.Id())
return readArchitectDatatableRow(ctx, d, meta)
}
func deleteArchitectDatatableRow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
tableId, keyStr := splitDatatableRowId(d.Id())
if keyStr == "" {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Invalid Row ID %s", d.Id()), fmt.Errorf("keyStr is nil"))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archProxy := getArchitectDatatableRowProxy(sdkConfig)
log.Printf("Deleting Datatable Row %s", d.Id())
resp, err := archProxy.deleteArchitectDatatableRow(ctx, tableId, keyStr)
if err != nil {
if util.IsStatus404(resp) {
// Parent architect_datatable was probably deleted which caused the row to be deleted
log.Printf("Datatable row already deleted %s", d.Id())
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Datatable Row %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := archProxy.getArchitectDatatableRow(ctx, tableId, keyStr)
if err != nil {
if util.IsStatus404(resp) {
// Datatable deleted
log.Printf("Deleted architect_datatable row %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting architect_datatable row %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Datatable row %s still exists", d.Id()), resp))
})
}
package architect_datatable_row
import (
"context"
"encoding/json"
"errors"
"github.com/mitchellh/mapstructure"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"net/http"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
)
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *architectDatatableRowProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getArchitectDatatableFunc func(ctx context.Context, p *architectDatatableRowProxy, datatableId string, expanded string) (*Datatable, *platformclientv2.APIResponse, error)
type getAllArchitectDatatableFunc func(ctx context.Context, p *architectDatatableRowProxy) (*[]platformclientv2.Datatable, *platformclientv2.APIResponse, error)
type getAllArchitectDatatableRowsFunc func(ctx context.Context, p *architectDatatableRowProxy, tableId string) (*[]map[string]interface{}, *platformclientv2.APIResponse, error)
type getArchitectDatatableRowFunc func(ctx context.Context, p *architectDatatableRowProxy, tableId string, key string) (*map[string]interface{}, *platformclientv2.APIResponse, error)
type createArchitectDatatableRowFunc func(ctx context.Context, p *architectDatatableRowProxy, tableId string, row *map[string]interface{}) (*map[string]interface{}, *platformclientv2.APIResponse, error)
type updateArchitectDatatableRowFunc func(ctx context.Context, p *architectDatatableRowProxy, tableId string, key string, row *map[string]interface{}) (*map[string]interface{}, *platformclientv2.APIResponse, error)
type deleteArchitectDatatableRowFunc func(ctx context.Context, p *architectDatatableRowProxy, tableId string, rowId string) (*platformclientv2.APIResponse, error)
type architectDatatableRowProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createArchitectDatatableRowAttr createArchitectDatatableRowFunc
getArchitectDatatableAttr getArchitectDatatableFunc
getAllArchitectDatatableAttr getAllArchitectDatatableFunc
getAllArchitectDatatableRowsAttr getAllArchitectDatatableRowsFunc
getArchitectDatatableRowAttr getArchitectDatatableRowFunc
updateArchitectDatatableRowAttr updateArchitectDatatableRowFunc
deleteArchitectDatatableRowAttr deleteArchitectDatatableRowFunc
dataTableRowCache rc.CacheInterface[map[string]interface{}]
dataTableCache rc.CacheInterface[Datatable]
}
func newArchitectDatatableRowProxy(clientConfig *platformclientv2.Configuration) *architectDatatableRowProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
dataTableRowCache := rc.NewResourceCache[map[string]interface{}]()
dataTableCache := rc.NewResourceCache[Datatable]()
return &architectDatatableRowProxy{
clientConfig: clientConfig,
architectApi: api,
dataTableRowCache: dataTableRowCache,
dataTableCache: dataTableCache,
getArchitectDatatableAttr: getArchitectDatatableFn,
getAllArchitectDatatableAttr: getAllArchitectDatatableFn,
getAllArchitectDatatableRowsAttr: getAllArchitectDatatableRowsFn,
getArchitectDatatableRowAttr: getArchitectDataTableRowFn,
createArchitectDatatableRowAttr: createArchitectDatatableRowFn,
updateArchitectDatatableRowAttr: updateArchitectDatatableRowFn,
deleteArchitectDatatableRowAttr: deleteArchitectDatatableRowFn,
}
}
func getArchitectDatatableRowProxy(clientConfig *platformclientv2.Configuration) *architectDatatableRowProxy {
if internalProxy == nil {
internalProxy = newArchitectDatatableRowProxy(clientConfig)
}
return internalProxy
}
func (p *architectDatatableRowProxy) getArchitectDatatable(ctx context.Context, id string, expanded string) (*Datatable, *platformclientv2.APIResponse, error) {
return p.getArchitectDatatableAttr(ctx, p, id, expanded)
}
func (p *architectDatatableRowProxy) getAllArchitectDatatable(ctx context.Context) (*[]platformclientv2.Datatable, *platformclientv2.APIResponse, error) {
return p.getAllArchitectDatatableAttr(ctx, p)
}
func (p *architectDatatableRowProxy) getAllArchitectDatatableRows(ctx context.Context, tableId string) (*[]map[string]interface{}, *platformclientv2.APIResponse, error) {
return p.getAllArchitectDatatableRowsAttr(ctx, p, tableId)
}
func (p *architectDatatableRowProxy) getArchitectDatatableRow(ctx context.Context, tableId string, key string) (*map[string]interface{}, *platformclientv2.APIResponse, error) {
return p.getArchitectDatatableRowAttr(ctx, p, tableId, key)
}
func (p *architectDatatableRowProxy) createArchitectDatatableRow(ctx context.Context, tableId string, row *map[string]interface{}) (*map[string]interface{}, *platformclientv2.APIResponse, error) {
return p.createArchitectDatatableRowAttr(ctx, p, tableId, row)
}
func (p *architectDatatableRowProxy) updateArchitectDatatableRow(ctx context.Context, tableId string, key string, row *map[string]interface{}) (*map[string]interface{}, *platformclientv2.APIResponse, error) {
return p.updateArchitectDatatableRowAttr(ctx, p, tableId, key, row)
}
func (p *architectDatatableRowProxy) deleteArchitectDatatableRow(ctx context.Context, tableId string, rowId string) (*platformclientv2.APIResponse, error) {
return p.deleteArchitectDatatableRowAttr(ctx, p, tableId, rowId)
}
func getAllArchitectDatatableFn(_ context.Context, p *architectDatatableRowProxy) (*[]platformclientv2.Datatable, *platformclientv2.APIResponse, error) {
var totalRecords []platformclientv2.Datatable
const pageSize = 100
tables, apiResponse, getErr := p.architectApi.GetFlowsDatatables("", 1, pageSize, "", "", nil, "")
if getErr != nil {
return &totalRecords, apiResponse, getErr
}
if tables.Entities == nil || len(*tables.Entities) == 0 {
return &totalRecords, apiResponse, nil
}
for _, table := range *tables.Entities {
totalRecords = append(totalRecords, table)
rc.SetCache(p.dataTableCache, *table.Id, *ConvertDatatable(table))
}
for pageNum := 2; pageNum <= *tables.PageCount; pageNum++ {
tables, apiResponse, getErr := p.architectApi.GetFlowsDatatables("", pageNum, pageSize, "", "", nil, "")
if getErr != nil {
return &totalRecords, apiResponse, getErr
}
if tables.Entities == nil || len(*tables.Entities) == 0 {
break
}
for _, table := range *tables.Entities {
totalRecords = append(totalRecords, table)
rc.SetCache(p.dataTableCache, *table.Id, *ConvertDatatable(table))
}
}
return &totalRecords, apiResponse, nil
}
func ConvertDatatable(master platformclientv2.Datatable) *Datatable {
var datatable Datatable
err := mapstructure.Decode(master, &datatable)
if err != nil {
log.Printf("Error converting the DataTable for id %v, error: %v", *master.Id, err)
return nil
}
return &datatable
}
func getArchitectDatatableFn(_ context.Context, p *architectDatatableRowProxy, datatableId string, expanded string) (*Datatable, *platformclientv2.APIResponse, error) {
eg := rc.GetCacheItem(p.dataTableCache, datatableId)
if eg != nil {
return eg, nil, nil
}
apiClient := &p.architectApi.Configuration.APIClient
// create path and map variables
path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables/" + datatableId
headerParams := make(map[string]string)
queryParams := make(map[string]string)
// oauth required
if p.architectApi.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken
}
// add default headers if any
for key := range p.architectApi.Configuration.DefaultHeader {
headerParams[key] = p.architectApi.Configuration.DefaultHeader[key]
}
queryParams["expand"] = apiClient.ParameterToString(expanded, "")
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *Datatable
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal(response.RawBody, &successPayload)
}
return successPayload, response, err
}
func getAllArchitectDatatableRowsFn(_ context.Context, p *architectDatatableRowProxy, tableId string) (*[]map[string]interface{}, *platformclientv2.APIResponse, error) {
var resources []map[string]interface{}
const pageSize = 100
rows, apiResponse, getErr := p.architectApi.GetFlowsDatatableRows(tableId, 1, pageSize, false, "")
if getErr != nil {
return nil, apiResponse, getErr
}
if rows.Entities == nil || len(*rows.Entities) == 0 {
return &resources, apiResponse, nil
}
for _, row := range *rows.Entities {
resources = append(resources, row)
if keyVal, ok := row["key"]; ok {
rc.SetCache(p.dataTableRowCache, tableId+"_"+keyVal.(string), row)
}
}
for pageNum := 2; pageNum <= *rows.PageCount; pageNum++ {
rows, apiResponse, getErr := p.architectApi.GetFlowsDatatableRows(tableId, pageNum, pageSize, false, "")
if getErr != nil {
return nil, apiResponse, getErr
}
if rows.Entities == nil || len(*rows.Entities) == 0 {
break
}
for _, row := range *rows.Entities {
resources = append(resources, row)
if keyVal, ok := row["key"]; ok {
rc.SetCache(p.dataTableRowCache, tableId+"_"+keyVal.(string), row)
}
}
}
return &resources, apiResponse, nil
}
func getArchitectDataTableRowFn(_ context.Context, p *architectDatatableRowProxy, tableId string, key string) (*map[string]interface{}, *platformclientv2.APIResponse, error) {
eg := rc.GetCacheItem(p.dataTableRowCache, tableId+"_"+key)
if eg != nil {
return eg, nil, nil
}
return p.architectApi.GetFlowsDatatableRow(tableId, key, false)
}
func createArchitectDatatableRowFn(_ context.Context, p *architectDatatableRowProxy, tableId string, row *map[string]interface{}) (*map[string]interface{}, *platformclientv2.APIResponse, error) {
return p.architectApi.PostFlowsDatatableRows(tableId, *row)
}
func updateArchitectDatatableRowFn(_ context.Context, p *architectDatatableRowProxy, tableId string, key string, row *map[string]interface{}) (*map[string]interface{}, *platformclientv2.APIResponse, error) {
return p.architectApi.PutFlowsDatatableRow(tableId, key, *row)
}
func deleteArchitectDatatableRowFn(_ context.Context, p *architectDatatableRowProxy, tableId string, rowId string) (*platformclientv2.APIResponse, error) {
return p.architectApi.DeleteFlowsDatatableRow(tableId, rowId)
}
package architect_datatable_row
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
)
const resourceName = "genesyscloud_architect_datatable_row"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceArchitectDatatableRow())
//No Datasource defined
regInstance.RegisterExporter(resourceName, ArchitectDatatableRowExporter())
}
func ArchitectDatatableRowExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllArchitectDatatableRows),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"datatable_id": {RefType: "genesyscloud_architect_datatable"},
},
JsonEncodeAttributes: []string{"properties_json"},
}
}
func ResourceArchitectDatatableRow() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Architect Datatable Row",
CreateContext: provider.CreateWithPooledClient(createArchitectDatatableRow),
ReadContext: provider.ReadWithPooledClient(readArchitectDatatableRow),
UpdateContext: provider.UpdateWithPooledClient(updateArchitectDatatableRow),
DeleteContext: provider.DeleteWithPooledClient(deleteArchitectDatatableRow),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"datatable_id": {
Description: "Datatable ID that contains this row. If this is changed, a new row is created.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"key_value": {
Description: "Value for this row's key. If this is changed, a new row is created.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"properties_json": {
Description: "JSON object containing properties and values for this row. Defaults will be set for missing properties.",
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
},
CustomizeDiff: customizeDatatableRowDiff,
}
}
package architect_emergencygroup
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceEmergencyGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
ap := getArchitectEmergencyGroupProxy(sdkConfig)
name := d.Get("name").(string)
// Query emergency group by name. Retry in case search has not yet indexed the emergency group.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
emergencyGroups, resp, getErr := ap.getArchitectEmergencyGroupIdByName(ctx, name)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting emergency group %s | error: %s", name, getErr), resp))
}
if emergencyGroups.Entities == nil || len(*emergencyGroups.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No emergency groups found with name %s", name), resp))
}
emergencyGroup := (*emergencyGroups.Entities)[0]
d.SetId(*emergencyGroup.Id)
return nil
})
}
package architect_emergencygroup
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *architectEmergencyGroupProxy
type createArchitectEmergencyGroupFunc func(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroup platformclientv2.Emergencygroup) (*platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error)
type getAllArchitectEmergencyGroupFunc func(ctx context.Context, p *architectEmergencyGroupProxy) (*[]platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error)
type getArchitectEmergencyGroupFunc func(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroupId string) (emergencyGroup *platformclientv2.Emergencygroup, apiResponse *platformclientv2.APIResponse, err error)
type updateArchitectEmergencyGroupFunc func(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroupId string, emergencyGroup platformclientv2.Emergencygroup) (*platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error)
type deleteArchitectEmergencyGroupFunc func(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroupId string) (*platformclientv2.APIResponse, error)
type getArchitectEmergencyGroupIdByNameFunc func(ctx context.Context, p *architectEmergencyGroupProxy, name string) (emergencyGroup *platformclientv2.Emergencygrouplisting, apiResponse *platformclientv2.APIResponse, err error)
type architectEmergencyGroupProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createArchitectEmergencyGroupAttr createArchitectEmergencyGroupFunc
getAllArchitectEmergencyGroupAttr getAllArchitectEmergencyGroupFunc
getArchitectEmergencyGroupAttr getArchitectEmergencyGroupFunc
getArchitectEmergencyGroupIdByNameAttr getArchitectEmergencyGroupIdByNameFunc
updateArchitectEmergencyGroupAttr updateArchitectEmergencyGroupFunc
deleteArchitectEmergencyGroupAttr deleteArchitectEmergencyGroupFunc
}
func newArchitectEmergencyGroupProxy(clientConfig *platformclientv2.Configuration) *architectEmergencyGroupProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &architectEmergencyGroupProxy{
clientConfig: clientConfig,
architectApi: api,
createArchitectEmergencyGroupAttr: createArchitectEmergencyGroupFn,
getAllArchitectEmergencyGroupAttr: getAllArchitectEmergencyGroupFn,
getArchitectEmergencyGroupAttr: getArchitectEmergencyGroupFn,
updateArchitectEmergencyGroupAttr: updateArchitectEmergencyGroupFn,
getArchitectEmergencyGroupIdByNameAttr: getArchitectEmergencyGroupIdByNameFn,
deleteArchitectEmergencyGroupAttr: deleteArchitectEmergencyGroupFn,
}
}
func getArchitectEmergencyGroupProxy(clientConfig *platformclientv2.Configuration) *architectEmergencyGroupProxy {
if internalProxy == nil {
internalProxy = newArchitectEmergencyGroupProxy(clientConfig)
}
return internalProxy
}
func (p *architectEmergencyGroupProxy) getAllArchitectEmergencyGroups(ctx context.Context) (*[]platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error) {
return p.getAllArchitectEmergencyGroupAttr(ctx, p)
}
func (p *architectEmergencyGroupProxy) getArchitectEmergencyGroup(ctx context.Context, emergencyGroupId string) (*platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error) {
return p.getArchitectEmergencyGroupAttr(ctx, p, emergencyGroupId)
}
func (p *architectEmergencyGroupProxy) updateArchitectEmergencyGroup(ctx context.Context, emergencyGroupId string, emergencyGroup platformclientv2.Emergencygroup) (*platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error) {
return p.updateArchitectEmergencyGroupAttr(ctx, p, emergencyGroupId, emergencyGroup)
}
func (p *architectEmergencyGroupProxy) getArchitectEmergencyGroupIdByName(ctx context.Context, name string) (emergencyGroup *platformclientv2.Emergencygrouplisting, apiResponse *platformclientv2.APIResponse, err error) {
return p.getArchitectEmergencyGroupIdByNameAttr(ctx, p, name)
}
func (p *architectEmergencyGroupProxy) deleteArchitectEmergencyGroup(ctx context.Context, emergencyGroupId string) (*platformclientv2.APIResponse, error) {
return p.deleteArchitectEmergencyGroupAttr(ctx, p, emergencyGroupId)
}
func (p *architectEmergencyGroupProxy) createArchitectEmergencyGroup(ctx context.Context, emergencyGroup platformclientv2.Emergencygroup) (*platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error) {
return p.createArchitectEmergencyGroupAttr(ctx, p, emergencyGroup)
}
func getAllArchitectEmergencyGroupFn(ctx context.Context, p *architectEmergencyGroupProxy) (*[]platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error) {
var totalRecords []platformclientv2.Emergencygroup
const pageSize = 100
emergencyGroupConfigs, resp, getErr := p.architectApi.GetArchitectEmergencygroups(1, pageSize, "", "", "")
if getErr != nil {
return nil, resp, fmt.Errorf("Failed to get page of emergency group configs: %v", getErr)
}
if emergencyGroupConfigs.Entities == nil || len(*emergencyGroupConfigs.Entities) == 0 {
return &totalRecords, nil, nil
}
totalRecords = append(totalRecords, *emergencyGroupConfigs.Entities...)
for pageNum := 2; pageNum <= *emergencyGroupConfigs.PageCount; pageNum++ {
emergencyGroupConfigs, resp, getErr := p.architectApi.GetArchitectEmergencygroups(pageNum, pageSize, "", "", "")
if getErr != nil {
return nil, resp, fmt.Errorf("Failed to get page of emergency group configs: %v", getErr)
}
if emergencyGroupConfigs.Entities == nil || len(*emergencyGroupConfigs.Entities) == 0 {
break
}
totalRecords = append(totalRecords, *emergencyGroupConfigs.Entities...)
}
return &totalRecords, nil, nil
}
func getArchitectEmergencyGroupFn(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroupId string) (emergencyGroup *platformclientv2.Emergencygroup, apiResponse *platformclientv2.APIResponse, err error) {
return p.architectApi.GetArchitectEmergencygroup(emergencyGroupId)
}
func getArchitectEmergencyGroupIdByNameFn(ctx context.Context, p *architectEmergencyGroupProxy, name string) (emergencyGroup *platformclientv2.Emergencygrouplisting, apiResponse *platformclientv2.APIResponse, err error) {
const pageNum = 1
const pageSize = 100
return p.architectApi.GetArchitectEmergencygroups(pageNum, pageSize, "", "", name)
}
func updateArchitectEmergencyGroupFn(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroupId string, emergencyGroup platformclientv2.Emergencygroup) (*platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error) {
return p.architectApi.PutArchitectEmergencygroup(emergencyGroupId, emergencyGroup)
}
func deleteArchitectEmergencyGroupFn(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroupId string) (*platformclientv2.APIResponse, error) {
return p.architectApi.DeleteArchitectEmergencygroup(emergencyGroupId)
}
func createArchitectEmergencyGroupFn(ctx context.Context, p *architectEmergencyGroupProxy, emergencyGroup platformclientv2.Emergencygroup) (*platformclientv2.Emergencygroup, *platformclientv2.APIResponse, error) {
return p.architectApi.PostArchitectEmergencygroups(emergencyGroup)
}
package architect_emergencygroup
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllEmergencyGroups(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
ap := getArchitectEmergencyGroupProxy(clientConfig)
emergencyGroupConfigs, resp, getErr := ap.getAllArchitectEmergencyGroups(ctx)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get Architect Emergency Groups error: %s", getErr), resp)
}
for _, emergencyGroupConfig := range *emergencyGroupConfigs {
if emergencyGroupConfig.State != nil && *emergencyGroupConfig.State != "deleted" {
resources[*emergencyGroupConfig.Id] = &resourceExporter.ResourceMeta{Name: *emergencyGroupConfig.Name}
}
}
return resources, nil
}
func createEmergencyGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
divisionId := d.Get("division_id").(string)
enabled := d.Get("enabled").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectEmergencyGroupProxy(sdkConfig)
emergencyGroup := platformclientv2.Emergencygroup{
Name: &name,
Enabled: &enabled,
EmergencyCallFlows: buildSdkEmergencyGroupCallFlows(d),
}
// Optional attributes
if description != "" {
emergencyGroup.Description = &description
}
if divisionId != "" {
emergencyGroup.Division = &platformclientv2.Writabledivision{Id: &divisionId}
}
log.Printf("Creating emergency group %s", name)
eGroup, resp, err := ap.createArchitectEmergencyGroup(ctx, emergencyGroup)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create emergency group %s error: %s", d.Id(), err), resp)
}
d.SetId(*eGroup.Id)
log.Printf("Created emergency group %s %s", name, *eGroup.Id)
return readEmergencyGroup(ctx, d, meta)
}
func readEmergencyGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectEmergencyGroupProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectEmergencyGroup(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading emergency group %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
emergencyGroup, resp, getErr := ap.getArchitectEmergencyGroup(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read emergency group %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read emergency group %s | error: %s", d.Id(), getErr), resp))
}
if emergencyGroup.State != nil && *emergencyGroup.State == "deleted" {
d.SetId("")
return nil
}
_ = d.Set("name", *emergencyGroup.Name)
_ = d.Set("division_id", *emergencyGroup.Division.Id)
resourcedata.SetNillableValue(d, "description", emergencyGroup.Description)
resourcedata.SetNillableValue(d, "enabled", emergencyGroup.Enabled)
if emergencyGroup.EmergencyCallFlows != nil && len(*emergencyGroup.EmergencyCallFlows) > 0 {
_ = d.Set("emergency_call_flows", flattenEmergencyCallFlows(*emergencyGroup.EmergencyCallFlows))
} else {
_ = d.Set("emergency_call_flows", nil)
}
log.Printf("Read emergency group %s %s", d.Id(), *emergencyGroup.Name)
return cc.CheckState(d)
})
}
func updateEmergencyGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
divisionId := d.Get("division_id").(string)
enabled := d.Get("enabled").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectEmergencyGroupProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current emergency group version
emergencyGroup, resp, getErr := ap.getArchitectEmergencyGroup(ctx, d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read emergency group %s error: %s", d.Id(), getErr), resp)
}
log.Printf("Updating emergency group %s", name)
updatedEmergencyGroup := platformclientv2.Emergencygroup{
Name: &name,
Division: &platformclientv2.Writabledivision{Id: &divisionId},
Description: &description,
Version: emergencyGroup.Version,
State: emergencyGroup.State,
Enabled: &enabled,
EmergencyCallFlows: buildSdkEmergencyGroupCallFlows(d),
}
_, resp, putErr := ap.updateArchitectEmergencyGroup(ctx, d.Id(), updatedEmergencyGroup)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update emergency group %s error: %s", d.Id(), putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Finished updating emergency group %s", name)
return readEmergencyGroup(ctx, d, meta)
}
func deleteEmergencyGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectEmergencyGroupProxy(sdkConfig)
log.Printf("Deleting emergency group %s", d.Id())
resp, err := ap.deleteArchitectEmergencyGroup(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update emergency group %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
emergencyGroup, resp, err := ap.getArchitectEmergencyGroup(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// group deleted
log.Printf("Deleted emergency group %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting emergency group %s | error: %s", d.Id(), err), resp))
}
if emergencyGroup.State != nil && *emergencyGroup.State == "deleted" {
// group deleted
log.Printf("Deleted emergency group %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("emergency group %s still exists", d.Id()), resp))
})
}
package architect_emergencygroup
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_architect_emergencygroup"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceArchitectEmergencyGroup())
regInstance.RegisterDataSource(resourceName, DataSourceArchitectEmergencyGroup())
regInstance.RegisterExporter(resourceName, ArchitectEmergencyGroupExporter())
}
func ResourceArchitectEmergencyGroup() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Architect Emergency Group",
CreateContext: provider.CreateWithPooledClient(createEmergencyGroup),
ReadContext: provider.ReadWithPooledClient(readEmergencyGroup),
UpdateContext: provider.UpdateWithPooledClient(updateEmergencyGroup),
DeleteContext: provider.DeleteWithPooledClient(deleteEmergencyGroup),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the emergency group. Note: If the name is changed, the emergency group is dropped and recreated with a new ID. This can cause an Architect flow to be invalid if it references the old emergency group",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"division_id": {
Description: "The division to which this emergency group will belong. If not set, the home division will be used.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Description: "Description of the emergency group.",
Type: schema.TypeString,
Optional: true,
},
"enabled": {
Description: "The state of the emergency group. Defaults to false/inactive.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"emergency_call_flows": {
Description: "The emergency call flows for this emergency group.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"emergency_flow_id": {
Description: "The ID of the connected call flow.",
Type: schema.TypeString,
Required: true,
},
"ivr_ids": {
Description: "The IDs of the connected IVRs.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
},
}
}
func DataSourceArchitectEmergencyGroup() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Emergency Groups. Select an emergency group by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceEmergencyGroupRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Emergency Group name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func ArchitectEmergencyGroupExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllEmergencyGroups),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
"emergency_call_flows.emergency_flow_id": {RefType: "genesyscloud_flow"},
"emergency_call_flows.ivr_ids": {RefType: "genesyscloud_architect_ivr"},
},
}
}
package architect_emergencygroup
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func buildSdkEmergencyGroupCallFlows(d *schema.ResourceData) *[]platformclientv2.Emergencycallflow {
var allCallFlows []platformclientv2.Emergencycallflow
if callFlows, ok := d.GetOk("emergency_call_flows"); ok {
for _, callFlow := range callFlows.([]interface{}) {
callFlowSettings := callFlow.(map[string]interface{})
var currentCallFlow platformclientv2.Emergencycallflow
if flowID, ok := callFlowSettings["emergency_flow_id"].(string); ok {
currentCallFlow.EmergencyFlow = &platformclientv2.Domainentityref{Id: &flowID}
}
if ivrIds, ok := callFlowSettings["ivr_ids"]; ok {
ids := ivrIds.(*schema.Set).List()
if len(ids) > 0 {
sdkIvrIds := make([]platformclientv2.Domainentityref, len(ids))
for i, id := range ids {
ivrID := id.(string)
sdkIvrIds[i] = platformclientv2.Domainentityref{Id: &ivrID}
}
currentCallFlow.Ivrs = &sdkIvrIds
}
}
allCallFlows = append(allCallFlows, currentCallFlow)
}
}
return &allCallFlows
}
func flattenEmergencyCallFlows(emergencyCallFlows []platformclientv2.Emergencycallflow) []interface{} {
callFlows := make([]interface{}, len(emergencyCallFlows))
for i, callFlow := range emergencyCallFlows {
callFlowSettings := make(map[string]interface{})
if callFlow.EmergencyFlow != nil {
callFlowSettings["emergency_flow_id"] = *callFlow.EmergencyFlow.Id
}
if callFlow.Ivrs != nil && len(*callFlow.Ivrs) > 0 {
ivrIds := make([]interface{}, len(*callFlow.Ivrs))
for k, id := range *callFlow.Ivrs {
ivrIds[k] = *id.Id
}
callFlowSettings["ivr_ids"] = schema.NewSet(schema.HashString, ivrIds)
}
callFlows[i] = callFlowSettings
}
return callFlows
}
package architect_flow
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceFlowRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
p := getArchitectFlowProxy(sdkConfig)
name := d.Get("name").(string)
// Query flow by name. Retry in case search has not yet indexed the flow.
return util.WithRetries(ctx, 5*time.Second, func() *retry.RetryError {
flows, resp, getErr := p.GetAllFlows(ctx)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting flow %s | error: %s", name, getErr), resp))
}
if flows == nil || len(*flows) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no flows found with name %s", name), resp))
}
for _, entity := range *flows {
if *entity.Name == name {
d.SetId(*entity.Id)
return nil
}
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no flows found with name %s", name), resp))
})
}
package architect_flow
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
)
var internalProxy *architectFlowProxy
type getArchitectFunc func(context.Context, *architectFlowProxy, string) (*platformclientv2.Flow, *platformclientv2.APIResponse, error)
type forceUnlockFlowFunc func(context.Context, *architectFlowProxy, string) (*platformclientv2.APIResponse, error)
type deleteArchitectFlowFunc func(context.Context, *architectFlowProxy, string) (*platformclientv2.APIResponse, error)
type createArchitectFlowJobsFunc func(context.Context, *architectFlowProxy) (*platformclientv2.Registerarchitectjobresponse, *platformclientv2.APIResponse, error)
type getArchitectFlowJobsFunc func(context.Context, *architectFlowProxy, string) (*platformclientv2.Architectjobstateresponse, *platformclientv2.APIResponse, error)
type getAllArchitectFlowsFunc func(context.Context, *architectFlowProxy) (*[]platformclientv2.Flow, *platformclientv2.APIResponse, error)
type architectFlowProxy struct {
clientConfig *platformclientv2.Configuration
api *platformclientv2.ArchitectApi
getArchitectFlowAttr getArchitectFunc
getAllArchitectFlowsAttr getAllArchitectFlowsFunc
forceUnlockFlowAttr forceUnlockFlowFunc
deleteArchitectFlowAttr deleteArchitectFlowFunc
createArchitectFlowJobsAttr createArchitectFlowJobsFunc
getArchitectFlowJobsAttr getArchitectFlowJobsFunc
flowCache rc.CacheInterface[platformclientv2.Flow]
}
func newArchitectFlowProxy(clientConfig *platformclientv2.Configuration) *architectFlowProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
flowCache := rc.NewResourceCache[platformclientv2.Flow]()
return &architectFlowProxy{
clientConfig: clientConfig,
api: api,
getArchitectFlowAttr: getArchitectFlowFn,
getAllArchitectFlowsAttr: getAllArchitectFlowsFn,
forceUnlockFlowAttr: forceUnlockFlowFn,
deleteArchitectFlowAttr: deleteArchitectFlowFn,
createArchitectFlowJobsAttr: createArchitectFlowJobsFn,
getArchitectFlowJobsAttr: getArchitectFlowJobsFn,
flowCache: flowCache,
}
}
func getArchitectFlowProxy(clientConfig *platformclientv2.Configuration) *architectFlowProxy {
if internalProxy == nil {
internalProxy = newArchitectFlowProxy(clientConfig)
}
return internalProxy
}
func (a *architectFlowProxy) GetFlow(ctx context.Context, id string) (*platformclientv2.Flow, *platformclientv2.APIResponse, error) {
return a.getArchitectFlowAttr(ctx, a, id)
}
func (a *architectFlowProxy) ForceUnlockFlow(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return a.forceUnlockFlowAttr(ctx, a, id)
}
func (a *architectFlowProxy) DeleteFlow(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return a.deleteArchitectFlowAttr(ctx, a, id)
}
func (a *architectFlowProxy) CreateFlowsDeployJob(ctx context.Context) (*platformclientv2.Registerarchitectjobresponse, *platformclientv2.APIResponse, error) {
return a.createArchitectFlowJobsAttr(ctx, a)
}
func (a *architectFlowProxy) GetFlowsDeployJob(ctx context.Context, jobId string) (*platformclientv2.Architectjobstateresponse, *platformclientv2.APIResponse, error) {
return a.getArchitectFlowJobsAttr(ctx, a, jobId)
}
func (a *architectFlowProxy) GetAllFlows(ctx context.Context) (*[]platformclientv2.Flow, *platformclientv2.APIResponse, error) {
return a.getAllArchitectFlowsAttr(ctx, a)
}
func getArchitectFlowFn(_ context.Context, p *architectFlowProxy, id string) (*platformclientv2.Flow, *platformclientv2.APIResponse, error) {
flow := rc.GetCacheItem(p.flowCache, id)
if flow != nil {
return flow, nil, nil
}
return p.api.GetFlow(id, false)
}
func forceUnlockFlowFn(_ context.Context, p *architectFlowProxy, flowId string) (*platformclientv2.APIResponse, error) {
log.Printf("Attempting to perform an unlock on flow: %s", flowId)
_, resp, err := p.api.PostFlowsActionsUnlock(flowId)
return resp, err
}
func deleteArchitectFlowFn(_ context.Context, p *architectFlowProxy, flowId string) (*platformclientv2.APIResponse, error) {
return p.api.DeleteFlow(flowId)
}
func createArchitectFlowJobsFn(_ context.Context, p *architectFlowProxy) (*platformclientv2.Registerarchitectjobresponse, *platformclientv2.APIResponse, error) {
return p.api.PostFlowsJobs()
}
func getArchitectFlowJobsFn(_ context.Context, p *architectFlowProxy, jobId string) (*platformclientv2.Architectjobstateresponse, *platformclientv2.APIResponse, error) {
return p.api.GetFlowsJob(jobId, []string{"messages"})
}
func getAllArchitectFlowsFn(ctx context.Context, p *architectFlowProxy) (*[]platformclientv2.Flow, *platformclientv2.APIResponse, error) {
const pageSize = 100
var totalFlows []platformclientv2.Flow
flows, resp, err := p.api.GetFlows(nil, 1, pageSize, "", "", nil, "", "", "", "", "", "", "", "", false, true, "", "", nil)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get page of flows: %v %v", err, resp)
}
if flows.Entities == nil || len(*flows.Entities) == 0 {
return &totalFlows, nil, nil
}
totalFlows = append(totalFlows, *flows.Entities...)
for pageNum := 2; pageNum <= *flows.PageCount; pageNum++ {
flows, resp, err := p.api.GetFlows(nil, pageNum, pageSize, "", "", nil, "", "", "", "", "", "", "", "", false, true, "", "", nil)
if err != nil {
return nil, resp, fmt.Errorf("failed to get page %d of flows: %v", pageNum, err)
}
if flows.Entities == nil || len(*flows.Entities) == 0 {
break
}
totalFlows = append(totalFlows, *flows.Entities...)
}
for _, flow := range totalFlows {
rc.SetCache(p.flowCache, *flow.Id, flow)
}
return &totalFlows, nil, nil
}
package architect_flow
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/validators"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const (
resourceName = "genesyscloud_flow"
)
// SetRegistrar registers all resources, data sources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceArchitectFlow())
l.RegisterResource(resourceName, ResourceArchitectFlow())
l.RegisterExporter(resourceName, ArchitectFlowExporter())
}
func ArchitectFlowExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllFlows),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{},
UnResolvableAttributes: map[string]*schema.Schema{
"filepath": ResourceArchitectFlow().Schema["filepath"],
},
CustomFlowResolver: map[string]*resourceExporter.CustomFlowResolver{
"file_content_hash": {ResolverFunc: resourceExporter.FileContentHashResolver},
},
}
}
func ResourceArchitectFlow() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Flow`,
CreateContext: provider.CreateWithPooledClient(createFlow),
UpdateContext: provider.UpdateWithPooledClient(updateFlow),
ReadContext: provider.ReadWithPooledClient(readFlow),
DeleteContext: provider.DeleteWithPooledClient(deleteFlow),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"filepath": {
Description: "YAML file path for flow configuration. Note: Changing the flow name will result in the creation of a new flow with a new GUID, while the original flow will persist in your org.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validators.ValidatePath,
},
"file_content_hash": {
Description: "Hash value of the YAML file content. Used to detect changes.",
Type: schema.TypeString,
Required: true,
},
"substitutions": {
Description: "A substitution is a key value pair where the key is the value you want to replace, and the value is the value to substitute in its place.",
Type: schema.TypeMap,
Optional: true,
},
"force_unlock": {
Description: `Will perform a force unlock on an architect flow before beginning the publication process. NOTE: The force unlock publishes the 'draft'
architect flow and then publishes the flow named in this resource. This mirrors the behavior found in the archy CLI tool.`,
Type: schema.TypeBool,
Optional: true,
},
},
}
}
func DataSourceArchitectFlow() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Flows. Select a flow by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceFlowRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Flow name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package architect_flow
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"log"
"path/filepath"
"strconv"
"strings"
)
func isForceUnlockEnabled(d *schema.ResourceData) bool {
forceUnlock := d.Get("force_unlock").(bool)
log.Printf("ForceUnlock: %v, id %v", forceUnlock, d.Id())
if forceUnlock && d.Id() != "" {
return true
}
return false
}
func GenerateFlowResource(resourceID, srcFile, fileContent string, forceUnlock bool, substitutions ...string) string {
fullyQualifiedPath, _ := filepath.Abs(srcFile)
if fileContent != "" {
updateFile(srcFile, fileContent)
}
flowResourceStr := fmt.Sprintf(`resource "genesyscloud_flow" "%s" {
filepath = %s
file_content_hash = filesha256(%s)
force_unlock = %v
%s
}
`, resourceID, strconv.Quote(srcFile), strconv.Quote(fullyQualifiedPath), forceUnlock, strings.Join(substitutions, "\n"))
return flowResourceStr
}
// setFileContentHashToNil This operation is required after a flow update fails because we want Terraform to detect changes
// in the file content hash and re-attempt an update, should the user re-run terraform apply without making changes to the file contents
func setFileContentHashToNil(d *schema.ResourceData) {
_ = d.Set("file_content_hash", nil)
}
package architect_flow
import (
"context"
"fmt"
"log"
"net/http"
"os"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util/files"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllFlows(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
p := getArchitectFlowProxy(clientConfig)
flows, resp, err := p.GetAllFlows(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to get architect flows %v", err), resp)
}
for _, flow := range *flows {
//DEVTOOLING-393: Putting this in here to deal with the situation where Cesar's BCP app is reliant on the naming structure
//This should be removed once the CX as Code architect export process is complete and will export files with the type in the name.
overrideBCPNaming := os.Getenv("OVERRIDE_BCP_NAMING")
if overrideBCPNaming != "" {
resources[*flow.Id] = &resourceExporter.ResourceMeta{Name: *flow.Name}
continue
}
//This is our go forward naming standard for flows.
resources[*flow.Id] = &resourceExporter.ResourceMeta{Name: *flow.VarType + "_" + *flow.Name}
}
return resources, nil
}
func readFlow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectFlowProxy(sdkConfig)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
flow, resp, err := proxy.GetFlow(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read flow %s: %s", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read flow %s: %s", d.Id(), err), resp))
}
log.Printf("Read flow %s %s", d.Id(), *flow.Name)
return nil
})
}
func createFlow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating flow")
return updateFlow(ctx, d, meta)
}
func updateFlow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
p := getArchitectFlowProxy(sdkConfig)
log.Printf("Updating flow")
//Check to see if we need to force and unlock on an architect flow
if isForceUnlockEnabled(d) {
resp, err := p.ForceUnlockFlow(ctx, d.Id())
if err != nil {
setFileContentHashToNil(d)
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to unlock targeted flow %s with error %s", d.Id(), err), resp)
}
}
flowJob, response, err := p.CreateFlowsDeployJob(ctx)
if err != nil || response.Error != nil {
var errorString string
if err != nil {
errorString = err.Error()
} else {
errorString = response.ErrorMessage
}
setFileContentHashToNil(d)
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to register job %s", errorString), response)
}
presignedUrl := *flowJob.PresignedUrl
jobId := *flowJob.Id
headers := *flowJob.Headers
filePath := d.Get("filepath").(string)
substitutions := d.Get("substitutions").(map[string]interface{})
reader, _, err := files.DownloadOrOpenFile(filePath)
if err != nil {
setFileContentHashToNil(d)
return diag.FromErr(err)
}
s3Uploader := files.NewS3Uploader(reader, nil, substitutions, headers, "PUT", presignedUrl)
_, uploadErr := s3Uploader.UploadWithRetries(ctx, filePath, 20*time.Second)
if uploadErr != nil {
setFileContentHashToNil(d)
return diag.FromErr(uploadErr)
}
// Pre-define here before entering retry function, otherwise it will be overwritten
flowID := ""
retryErr := util.WithRetries(ctx, 16*time.Minute, func() *retry.RetryError {
flowJob, response, err := p.GetFlowsDeployJob(ctx, jobId)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error retrieving job status. JobID: %s, error: %s ", jobId, err), response))
}
if *flowJob.Status == "Failure" {
if flowJob.Messages == nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("flow publish failed. JobID: %s, no tracing messages available", jobId), response))
}
messages := make([]string, 0)
for _, m := range *flowJob.Messages {
messages = append(messages, *m.Text)
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("flow publish failed. JobID: %s, tracing messages: %v ", jobId, strings.Join(messages, "\n\n")), response))
}
if *flowJob.Status == "Success" {
flowID = *flowJob.Flow.Id
return nil
}
time.Sleep(15 * time.Second) // Wait 15 seconds for next retry
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Job (%s) could not finish in 16 minutes and timed out ", jobId), response))
})
if retryErr != nil {
setFileContentHashToNil(d)
return retryErr
}
if flowID == "" {
setFileContentHashToNil(d)
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to get the flowId from Architect Job (%s).", jobId), fmt.Errorf("FlowID is nil"))
}
d.SetId(flowID)
log.Printf("Updated flow %s. ", d.Id())
return readFlow(ctx, d, meta)
}
func deleteFlow(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
p := getArchitectFlowProxy(sdkConfig)
log.Printf("Deleting flow %s", d.Id())
//Check to see if we need to force
if isForceUnlockEnabled(d) {
resp, err := p.ForceUnlockFlow(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to unlock targeted flow %s with error %v", d.Id(), err), resp)
}
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
resp, err := p.DeleteFlow(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Flow deleted
log.Printf("Deleted Flow %s", d.Id())
return nil
}
if resp.StatusCode == http.StatusConflict {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting flow %s | error: %s", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting flow %s | error: %s", d.Id(), err), resp))
}
return nil
})
}
func updateFile(filepath, content string) {
file, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Println(err)
return
}
defer func(file *os.File) {
if err := file.Close(); err != nil {
log.Printf("failed to close file %s: %v", filepath, err)
}
}(file)
_, _ = file.WriteString(content)
}
package architect_grammar
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/provider"
util "terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_architect_grammar.go contains the data source implementation
for the resource.
*/
// dataSourceArchitectGrammarRead retrieves by name the id in question
func dataSourceArchitectGrammarRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newArchitectGrammarProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
grammarId, retryable, resp, err := proxy.getArchitectGrammarIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error grammar %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No grammar found with name %s", name), resp))
}
d.SetId(grammarId)
return nil
})
}
package architect_grammar
import (
"context"
"fmt"
"log"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_architect_grammar_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *architectGrammarProxy
// Type definitions for each func on our proxy so that we can easily mock them out later
type createArchitectGrammarFunc func(ctx context.Context, p *architectGrammarProxy, grammar *platformclientv2.Grammar) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error)
type getAllArchitectGrammarFunc func(ctx context.Context, p *architectGrammarProxy) (*[]platformclientv2.Grammar, *platformclientv2.APIResponse, error)
type getArchitectGrammarByIdFunc func(ctx context.Context, p *architectGrammarProxy, grammarId string) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error)
type getArchitectGrammarIdByNameFunc func(ctx context.Context, p *architectGrammarProxy, name string) (string, bool, *platformclientv2.APIResponse, error)
type updateArchitectGrammarFunc func(ctx context.Context, p *architectGrammarProxy, grammarId string, grammar *platformclientv2.Grammar) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error)
type deleteArchitectGrammarFunc func(ctx context.Context, p *architectGrammarProxy, grammarId string) (*platformclientv2.APIResponse, error)
// architectGrammarProxy contains all the methods that call genesys cloud APIs.
type architectGrammarProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createArchitectGrammarAttr createArchitectGrammarFunc
getAllArchitectGrammarAttr getAllArchitectGrammarFunc
getArchitectGrammarByIdAttr getArchitectGrammarByIdFunc
getArchitectGrammarIdByNameAttr getArchitectGrammarIdByNameFunc
updateArchitectGrammarAttr updateArchitectGrammarFunc
deleteArchitectGrammarAttr deleteArchitectGrammarFunc
grammarCache rc.CacheInterface[platformclientv2.Grammar]
}
// newArchitectGrammarProxy initializes the grammar proxy with all the data needed to communicate with Genesys Cloud
func newArchitectGrammarProxy(clientConfig *platformclientv2.Configuration) *architectGrammarProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
grammarCache := rc.NewResourceCache[platformclientv2.Grammar]()
return &architectGrammarProxy{
clientConfig: clientConfig,
architectApi: api,
createArchitectGrammarAttr: createArchitectGrammarFn,
getAllArchitectGrammarAttr: getAllArchitectGrammarFn,
getArchitectGrammarByIdAttr: getArchitectGrammarByIdFn,
getArchitectGrammarIdByNameAttr: getArchitectGrammarIdByNameFn,
updateArchitectGrammarAttr: updateArchitectGrammarFn,
deleteArchitectGrammarAttr: deleteArchitectGrammarFn,
grammarCache: grammarCache,
}
}
// getArchitectGrammarProxy acts as a singleton for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getArchitectGrammarProxy(clientConfig *platformclientv2.Configuration) *architectGrammarProxy {
if internalProxy == nil {
internalProxy = newArchitectGrammarProxy(clientConfig)
}
return internalProxy
}
// createArchitectGrammar creates a Genesys Cloud Architect Grammar
func (p *architectGrammarProxy) createArchitectGrammar(ctx context.Context, grammar *platformclientv2.Grammar) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
return p.createArchitectGrammarAttr(ctx, p, grammar)
}
// getAllArchitectGrammar retrieves all Genesys Cloud Architect Grammar
func (p *architectGrammarProxy) getAllArchitectGrammar(ctx context.Context) (*[]platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
return p.getAllArchitectGrammarAttr(ctx, p)
}
// getArchitectGrammarById returns a single Genesys Cloud Architect Grammar by ID
func (p *architectGrammarProxy) getArchitectGrammarById(ctx context.Context, grammarId string) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
return p.getArchitectGrammarByIdAttr(ctx, p, grammarId)
}
// getArchitectGrammarIdByName returns a single Genesys Cloud Architect Grammar by a name
func (p *architectGrammarProxy) getArchitectGrammarIdByName(ctx context.Context, name string) (string, bool, *platformclientv2.APIResponse, error) {
return p.getArchitectGrammarIdByNameAttr(ctx, p, name)
}
// updateArchitectGrammar updates a Genesys Cloud Architect Grammar
func (p *architectGrammarProxy) updateArchitectGrammar(ctx context.Context, grammarId string, grammar *platformclientv2.Grammar) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
return p.updateArchitectGrammarAttr(ctx, p, grammarId, grammar)
}
// deleteArchitectGrammar deletes a Genesys Cloud Architect Grammar by ID
func (p *architectGrammarProxy) deleteArchitectGrammar(ctx context.Context, grammarId string) (*platformclientv2.APIResponse, error) {
return p.deleteArchitectGrammarAttr(ctx, p, grammarId)
}
// createArchitectGrammarFn is an implementation function for creating a Genesys Cloud Architect Grammar
func createArchitectGrammarFn(_ context.Context, p *architectGrammarProxy, grammar *platformclientv2.Grammar) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
grammarSdk, resp, err := p.architectApi.PostArchitectGrammars(*grammar)
if err != nil {
return nil, resp, fmt.Errorf("failed to create grammar: %s %v", err, resp)
}
return grammarSdk, resp, nil
}
// getAllArchitectGrammarFn is the implementation for retrieving all Architect Grammars in Genesys Cloud
func getAllArchitectGrammarFn(_ context.Context, p *architectGrammarProxy) (*[]platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
var allGrammars []platformclientv2.Grammar
grammars, resp, err := p.architectApi.GetArchitectGrammars(1, 100, "", "", []string{}, "", "", "", true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get architect grammars: %v %v", err, resp)
}
if grammars.Entities == nil || len(*grammars.Entities) == 0 {
return &allGrammars, resp, nil
}
allGrammars = append(allGrammars, *grammars.Entities...)
for pageNum := 2; pageNum <= *grammars.PageCount; pageNum++ {
const pageSize = 100
grammars, resp, err := p.architectApi.GetArchitectGrammars(pageNum, pageSize, "", "", []string{}, "", "", "", true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get architect grammars: %v %v", err, resp)
}
if grammars.Entities == nil || len(*grammars.Entities) == 0 {
break
}
allGrammars = append(allGrammars, *grammars.Entities...)
}
for _, grammar := range allGrammars {
rc.SetCache(p.grammarCache, *grammar.Id, grammar)
}
return &allGrammars, resp, nil
}
// getArchitectGrammarByIdFn is an implementation of the function to get a Genesys Cloud Architect Grammar by ID
func getArchitectGrammarByIdFn(_ context.Context, p *architectGrammarProxy, grammarId string) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
grammar := rc.GetCacheItem(p.grammarCache, grammarId)
if grammar != nil {
return grammar, nil, nil
}
grammar, resp, err := p.architectApi.GetArchitectGrammar(grammarId, true)
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve grammar by id %s: %s", grammarId, err)
}
return grammar, resp, nil
}
// getArchitectGrammarIdByNameFn is an implementation of the function to get a Genesys Cloud Architect Grammar by name
func getArchitectGrammarIdByNameFn(ctx context.Context, p *architectGrammarProxy, name string) (string, bool, *platformclientv2.APIResponse, error) {
grammars, resp, err := getAllArchitectGrammarFn(ctx, p)
if err != nil {
return "", false, resp, err
}
if grammars == nil || len(*grammars) == 0 {
return "", true, resp, fmt.Errorf("no architect grammars found with name %s", name)
}
var grammar platformclientv2.Grammar
for _, grammarSdk := range *grammars {
if *grammarSdk.Name == name {
log.Printf("Retrieved the grammar id %s by name %s", *grammarSdk.Id, name)
grammar = grammarSdk
return *grammar.Id, false, resp, nil
}
}
return "", false, resp, fmt.Errorf("unable to find grammar with name %s", name)
}
// updateArchitectGrammarFn is an implementation of the function to update a Genesys Cloud Architect Grammar
func updateArchitectGrammarFn(_ context.Context, p *architectGrammarProxy, grammarId string, grammar *platformclientv2.Grammar) (*platformclientv2.Grammar, *platformclientv2.APIResponse, error) {
grammarSdk, resp, err := p.architectApi.PatchArchitectGrammar(grammarId, *grammar)
if err != nil {
return nil, resp, fmt.Errorf("failed to update grammar %s: %s", grammarId, err)
}
return grammarSdk, resp, nil
}
// deleteArchitectGrammarFn is an implementation function for deleting a Genesys Cloud Architect Grammar
func deleteArchitectGrammarFn(_ context.Context, p *architectGrammarProxy, grammarId string) (*platformclientv2.APIResponse, error) {
_, resp, err := p.architectApi.DeleteArchitectGrammar(grammarId)
if err != nil {
return resp, fmt.Errorf("failed to delete grammar: %s", err)
}
return resp, nil
}
package architect_grammar
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_architect_grammar.go contains all the methods that perform the core logic for a resource.
*/
// getAllAuthArchitectGrammar retrieves all the architect grammars via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthArchitectGrammar(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getArchitectGrammarProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
grammars, resp, err := proxy.getAllArchitectGrammar(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to retrieve all grammars: %s", err), resp)
}
for _, grammar := range *grammars {
resources[*grammar.Id] = &resourceExporter.ResourceMeta{Name: *grammar.Name}
}
return resources, nil
}
// createArchitectGrammar is used by the architect_grammar_language resource to create a Genesys cloud architect grammar
func createArchitectGrammar(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarProxy(sdkConfig)
architectGrammar := platformclientv2.Grammar{
Name: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
}
// Create grammar
log.Printf("Creating Architect Grammar %s", *architectGrammar.Name)
grammar, resp, err := proxy.createArchitectGrammar(ctx, &architectGrammar)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create grammar %s error: %s", d.Id(), err), resp)
}
d.SetId(*grammar.Id)
log.Printf("Created Architect Grammar %s", *grammar.Id)
return readArchitectGrammar(ctx, d, meta)
}
// readArchitectGrammar is used by the architect_grammar_language resource to read an architect grammar from genesys cloud.
func readArchitectGrammar(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectGrammar(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Architect Grammar %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
grammar, resp, getErr := proxy.getArchitectGrammarById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Architect Grammar %s: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Architect Grammar %s: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", grammar.Name)
resourcedata.SetNillableValue(d, "description", grammar.Description)
log.Printf("Read Architect Grammar %s", d.Id())
return cc.CheckState(d)
})
}
// updateArchitectGrammar is used by the architect_grammar_language resource to update an architect grammar in Genesys Cloud
func updateArchitectGrammar(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarProxy(sdkConfig)
architectGrammar := platformclientv2.Grammar{
Name: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
}
// Update grammar
log.Printf("Updating Architect Grammar %s", *architectGrammar.Name)
grammar, resp, err := proxy.updateArchitectGrammar(ctx, d.Id(), &architectGrammar)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update grammar: %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated Architect Grammar %s", *grammar.Id)
return readArchitectGrammar(ctx, d, meta)
}
// deleteArchitectGrammar is used by the architect_grammar_language resource to delete an architect grammar from Genesys cloud.
func deleteArchitectGrammar(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarProxy(sdkConfig)
resp, err := proxy.deleteArchitectGrammar(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete grammar %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getArchitectGrammarById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted Grammar %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting grammar %s: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("grammar %s still exists", d.Id()), resp))
})
}
func GenerateGrammarResource(
resourceId string,
name string,
description string,
) string {
return fmt.Sprintf(`
resource "genesyscloud_architect_grammar" "%s" {
name = "%s"
description = "%s"
}
`, resourceId, name, description)
}
package architect_grammar
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesyscloud_architect_grammar_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the architect_grammar resource.
3. The datasource schema definitions for the architect_grammar datasource.
4. The resource exporter configuration for the architect_grammar exporter.
*/
const resourceName = "genesyscloud_architect_grammar"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceArchitectGrammar())
regInstance.RegisterDataSource(resourceName, DataSourceArchitectGrammar())
regInstance.RegisterExporter(resourceName, ArchitectGrammarExporter())
}
// ResourceArchitectGrammar registers the genesyscloud_architect_grammar resource with Terraform
func ResourceArchitectGrammar() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud architect grammar`,
CreateContext: provider.CreateWithPooledClient(createArchitectGrammar),
ReadContext: provider.ReadWithPooledClient(readArchitectGrammar),
UpdateContext: provider.UpdateWithPooledClient(updateArchitectGrammar),
DeleteContext: provider.DeleteWithPooledClient(deleteArchitectGrammar),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: "The name of grammar",
Required: true,
Type: schema.TypeString,
},
`description`: {
Description: "Description of the grammar",
Optional: true,
Type: schema.TypeString,
},
},
}
}
// ArchitectGrammarExporter returns the resourceExporter object used to hold the genesyscloud_architect_grammar exporter's config
func ArchitectGrammarExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthArchitectGrammar),
}
}
// DataSourceArchitectGrammar registers the genesyscloud_architect_grammar data source
func DataSourceArchitectGrammar() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Architect Grammar. Select an Architect Grammar by name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceArchitectGrammarRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Architect grammar name.`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package architect_grammar_language
import (
"context"
"fmt"
"net/http"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
"terraform-provider-genesyscloud/genesyscloud/util/files"
"time"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type FileType int
const (
Dtmf FileType = iota
Voice
)
/*
The genesyscloud_architect_grammar_language_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *architectGrammarLanguageProxy
// Type definitions for each func on our proxy so that we can easily mock them out later
type createArchitectGrammarLanguageFunc func(ctx context.Context, p *architectGrammarLanguageProxy, language *platformclientv2.Grammarlanguage) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error)
type getArchitectGrammarLanguageByIdFunc func(ctx context.Context, p *architectGrammarLanguageProxy, grammarId string, languageCode string) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error)
type updateArchitectGrammarLanguageFunc func(ctx context.Context, p *architectGrammarLanguageProxy, grammarId string, languageCode string, language *platformclientv2.Grammarlanguage) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error)
type deleteArchitectGrammarLanguageFunc func(ctx context.Context, p *architectGrammarLanguageProxy, grammarId string, languageCode string) (*platformclientv2.APIResponse, error)
type getAllArchitectGrammarLanguageFunc func(ctx context.Context, p *architectGrammarLanguageProxy) (*[]platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error)
// architectGrammarLanguageProxy contains all the methods that call genesys cloud APIs.
type architectGrammarLanguageProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createArchitectGrammarLanguageAttr createArchitectGrammarLanguageFunc
getArchitectGrammarLanguageByIdAttr getArchitectGrammarLanguageByIdFunc
updateArchitectGrammarLanguageAttr updateArchitectGrammarLanguageFunc
deleteArchitectGrammarLanguageAttr deleteArchitectGrammarLanguageFunc
getAllArchitectGrammarLanguageAttr getAllArchitectGrammarLanguageFunc
grammarLanguageCache rc.CacheInterface[platformclientv2.Grammarlanguage]
}
// newArchitectGrammarLanguageProxy initializes the grammar Language proxy with all the data needed to communicate with Genesys Cloud
func newArchitectGrammarLanguageProxy(clientConfig *platformclientv2.Configuration) *architectGrammarLanguageProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
grammarLanguageCache := rc.NewResourceCache[platformclientv2.Grammarlanguage]()
return &architectGrammarLanguageProxy{
clientConfig: clientConfig,
architectApi: api,
createArchitectGrammarLanguageAttr: createArchitectGrammarLanguageFn,
getArchitectGrammarLanguageByIdAttr: getArchitectGrammarLanguageByIdFn,
updateArchitectGrammarLanguageAttr: updateArchitectGrammarLanguageFn,
deleteArchitectGrammarLanguageAttr: deleteArchitectGrammarLanguageFn,
getAllArchitectGrammarLanguageAttr: getAllArchitectGrammarLanguageFn,
grammarLanguageCache: grammarLanguageCache,
}
}
// getArchitectGrammarLanguageProxy acts as a singleton for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getArchitectGrammarLanguageProxy(clientConfig *platformclientv2.Configuration) *architectGrammarLanguageProxy {
if internalProxy == nil {
internalProxy = newArchitectGrammarLanguageProxy(clientConfig)
}
return internalProxy
}
// createArchitectGrammarLanguage creates a Genesys Cloud Architect Grammar Language
func (p *architectGrammarLanguageProxy) createArchitectGrammarLanguage(ctx context.Context, language *platformclientv2.Grammarlanguage) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
return p.createArchitectGrammarLanguageAttr(ctx, p, language)
}
// getArchitectGrammarLanguageById returns a single Genesys Cloud Architect Grammar Language by ID
func (p *architectGrammarLanguageProxy) getArchitectGrammarLanguageById(ctx context.Context, grammarId string, languageCode string) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
return p.getArchitectGrammarLanguageByIdAttr(ctx, p, grammarId, languageCode)
}
// updateArchitectGrammarLanguage updates a Genesys Cloud Architect Grammar Language
func (p *architectGrammarLanguageProxy) updateArchitectGrammarLanguage(ctx context.Context, grammarId string, languageCode string, language *platformclientv2.Grammarlanguage) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
return p.updateArchitectGrammarLanguageAttr(ctx, p, grammarId, languageCode, language)
}
// deleteArchitectGrammarLanguage deletes a Genesys Cloud Architect Grammar Language by ID
func (p *architectGrammarLanguageProxy) deleteArchitectGrammarLanguage(ctx context.Context, grammarId string, languageCode string) (*platformclientv2.APIResponse, error) {
return p.deleteArchitectGrammarLanguageAttr(ctx, p, grammarId, languageCode)
}
// getAllArchitectGrammarLanguage retrieves all Genesys Cloud Architect Grammar Languages
func (p *architectGrammarLanguageProxy) getAllArchitectGrammarLanguage(ctx context.Context) (*[]platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
return p.getAllArchitectGrammarLanguageAttr(ctx, p)
}
// createArchitectGrammarLanguageFn is an implementation function for creating a Genesys Cloud Architect Grammar Language
func createArchitectGrammarLanguageFn(_ context.Context, p *architectGrammarLanguageProxy, language *platformclientv2.Grammarlanguage) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
languageSdk, resp, err := p.architectApi.PostArchitectGrammarLanguages(*language.GrammarId, *language)
if err != nil {
return nil, resp, err
}
// Upload grammar voice file
if language.VoiceFileMetadata != nil && language.VoiceFileMetadata.FileName != nil {
if resp, err := uploadGrammarLanguageFile(p, language, *language.VoiceFileMetadata.FileName, Voice); err != nil {
return nil, resp, fmt.Errorf("failed to upload language voice file for grammar '%s': %s", *language.GrammarId, err)
}
}
// Upload grammar dtmf file
if language.DtmfFileMetadata != nil && language.DtmfFileMetadata.FileName != nil {
if resp, err := uploadGrammarLanguageFile(p, language, *language.DtmfFileMetadata.FileName, Dtmf); err != nil {
return nil, resp, fmt.Errorf("failed to upload language dtmf file for grammar '%s': %s", *language.GrammarId, err)
}
}
return languageSdk, resp, nil
}
// getArchitectGrammarLanguageByIdFn is an implementation of the function to get a Genesys Cloud Architect Grammar Language by ID
func getArchitectGrammarLanguageByIdFn(_ context.Context, p *architectGrammarLanguageProxy, grammarId string, languageCode string) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
language := rc.GetCacheItem(p.grammarLanguageCache, fmt.Sprintf("%s:%s", grammarId, languageCode))
if language != nil {
return language, nil, nil
}
return p.architectApi.GetArchitectGrammarLanguage(grammarId, languageCode)
}
// updateArchitectGrammarLanguageFn is an implementation of the function to update a Genesys Cloud Architect Grammar Language
func updateArchitectGrammarLanguageFn(_ context.Context, p *architectGrammarLanguageProxy, grammarId string, languageCode string, language *platformclientv2.Grammarlanguage) (*platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
languageUpdate := platformclientv2.Grammarlanguageupdate{
VoiceFileMetadata: language.VoiceFileMetadata,
DtmfFileMetadata: language.DtmfFileMetadata,
}
languageSdk, resp, err := p.architectApi.PatchArchitectGrammarLanguage(grammarId, languageCode, languageUpdate)
if err != nil {
return nil, resp, err
}
// Upload grammar voice file
if language.VoiceFileMetadata != nil && language.VoiceFileMetadata.FileName != nil {
if resp, err := uploadGrammarLanguageFile(p, language, *language.VoiceFileMetadata.FileName, Voice); err != nil {
return nil, resp, fmt.Errorf("failed to upload language voice file for grammar '%s': %s", *language.GrammarId, err)
}
}
// Upload grammar dtmf file
if language.DtmfFileMetadata != nil && language.DtmfFileMetadata.FileName != nil {
if resp, err := uploadGrammarLanguageFile(p, language, *language.DtmfFileMetadata.FileName, Dtmf); err != nil {
return nil, resp, fmt.Errorf("failed to upload language dtmf file for grammar '%s': %s", *language.GrammarId, err)
}
}
return languageSdk, resp, nil
}
// deleteArchitectGrammarLanguageFn is an implementation function for deleting a Genesys Cloud Architect Grammar Language
func deleteArchitectGrammarLanguageFn(_ context.Context, p *architectGrammarLanguageProxy, grammarId string, languageCode string) (response *platformclientv2.APIResponse, err error) {
return p.architectApi.DeleteArchitectGrammarLanguage(grammarId, languageCode)
}
// uploadGrammarLanguageFile is a function for uploading a grammar language file to Genesys cloud
func uploadGrammarLanguageFile(p *architectGrammarLanguageProxy, language *platformclientv2.Grammarlanguage, filePath string, fileType FileType) (*platformclientv2.APIResponse, error) {
var (
uploadResponse *platformclientv2.Uploadurlresponse
apiResponse *platformclientv2.APIResponse
err error
grammarId = *language.GrammarId
languageCode = *language.Language
uploadBody platformclientv2.Grammarfileuploadrequest
)
if fileType == Voice {
uploadBody.FileType = language.VoiceFileMetadata.FileType
uploadResponse, apiResponse, err = p.architectApi.PostArchitectGrammarLanguageFilesVoice(grammarId, languageCode, uploadBody)
}
if fileType == Dtmf {
uploadBody.FileType = language.DtmfFileMetadata.FileType
uploadResponse, apiResponse, err = p.architectApi.PostArchitectGrammarLanguageFilesDtmf(grammarId, languageCode, uploadBody)
}
if err != nil {
return apiResponse, fmt.Errorf("failed to get language file presignedUri: %s for file %s", err, filePath)
}
reader, _, err := files.DownloadOrOpenFile(filePath)
if err != nil {
return nil, fmt.Errorf("error opening file '%s': %v", filePath, err)
}
s3Uploader := files.NewS3Uploader(reader, nil, nil, *uploadResponse.Headers, http.MethodPut, *uploadResponse.Url)
if _, uploadErr := s3Uploader.UploadWithRetries(context.Background(), filePath, 20*time.Second); uploadErr != nil {
return nil, fmt.Errorf("failed to upload language file for grammar '%s': %v", *language.GrammarId, uploadErr)
}
return nil, nil
}
// getAllArchitectGrammarLanguageFn is the implementation for retrieving all Architect Grammars in Genesys Cloud
func getAllArchitectGrammarLanguageFn(_ context.Context, p *architectGrammarLanguageProxy) (*[]platformclientv2.Grammarlanguage, *platformclientv2.APIResponse, error) {
var allLanguages []platformclientv2.Grammarlanguage
grammars, resp, err := p.architectApi.GetArchitectGrammars(1, 100, "", "", []string{}, "", "", "", true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get architect grammar languages: %v", err)
}
if grammars.Entities == nil || len(*grammars.Entities) == 0 {
return &allLanguages, resp, nil
}
for _, grammar := range *grammars.Entities {
if grammar.Languages != nil {
allLanguages = append(allLanguages, *grammar.Languages...)
}
}
for pageNum := 2; pageNum <= *grammars.PageCount; pageNum++ {
const pageSize = 100
grammars, resp, err := p.architectApi.GetArchitectGrammars(pageNum, pageSize, "", "", []string{}, "", "", "", true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get architect grammar languages: %v", err)
}
if grammars.Entities == nil || len(*grammars.Entities) == 0 {
break
}
for _, grammar := range *grammars.Entities {
if grammar.Languages != nil {
allLanguages = append(allLanguages, *grammar.Languages...)
}
}
}
for _, language := range allLanguages {
rc.SetCache(p.grammarLanguageCache, fmt.Sprintf("%s:%s", *language.GrammarId, *language.Language), language)
}
return &allLanguages, resp, nil
}
package architect_grammar_language
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_architect_grammar_language.go contains all the methods that perform the core logic for a resource.
*/
// getAllAuthArchitectGrammarLanguage retrieves all of the architect grammar languages via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthArchitectGrammarLanguage(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getArchitectGrammarLanguageProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
languages, resp, err := proxy.getAllArchitectGrammarLanguage(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get grammar languages: %v", err), resp)
}
for _, language := range *languages {
languageId := *language.GrammarId + ":" + *language.Language
resources[languageId] = &resourceExporter.ResourceMeta{Name: *language.Language}
}
return resources, nil
}
// createArchitectGrammarLanguage is used by the architect_grammar_language resource to create a Genesys cloud architect grammar language
func createArchitectGrammarLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarLanguageProxy(sdkConfig)
architectGrammarLanguage := getArchitectGrammarLanguageFromResourceData(d)
log.Printf("Creating Architect Grammar Language %s for grammar %s", *architectGrammarLanguage.Language, *architectGrammarLanguage.GrammarId)
language, resp, err := proxy.createArchitectGrammarLanguage(ctx, &architectGrammarLanguage)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create grammar language: %s error %s", d.Id(), err), resp)
}
// Language id is always in format <grammar-id>:<language-code>
languageId := fmt.Sprintf("%s:%s", *language.GrammarId, *language.Language)
d.SetId(languageId)
log.Printf("Created Architect Grammar Language %s", languageId)
return readArchitectGrammarLanguage(ctx, d, meta)
}
// readArchitectGrammarLanguage is used by the architect_grammar_language resource to read an architect grammar language from genesys cloud.
func readArchitectGrammarLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarLanguageProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectGrammarLanguage(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Architect Grammar Language %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
grammarId, languageCode := splitLanguageId(d.Id())
language, resp, getErr := proxy.getArchitectGrammarLanguageById(ctx, grammarId, languageCode)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Architect Grammar Language %s: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Architect Grammar Language %s: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "grammar_id", language.GrammarId)
resourcedata.SetNillableValue(d, "language", language.Language)
if language.VoiceFileMetadata != nil {
_ = d.Set("voice_file_data", flattenGrammarLanguageFileMetadata(d, language.VoiceFileMetadata, Voice))
}
if language.DtmfFileMetadata != nil {
_ = d.Set("dtmf_file_data", flattenGrammarLanguageFileMetadata(d, language.DtmfFileMetadata, Dtmf))
}
log.Printf("Read Architect Grammar Language %s", d.Id())
return cc.CheckState(d)
})
}
func splitLanguageId(languageId string) (string, string) {
split := strings.SplitN(languageId, ":", 2)
if len(split) == 2 {
return split[0], split[1]
}
return "", ""
}
// updateArchitectGrammarLanguage is used by the architect_grammar_language resource to update an architect grammar language in Genesys Cloud
func updateArchitectGrammarLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarLanguageProxy(sdkConfig)
architectGrammarLanguage := getArchitectGrammarLanguageFromResourceData(d)
log.Printf("Updating Architect Grammar Language %s", d.Id())
_, resp, err := proxy.updateArchitectGrammarLanguage(ctx, *architectGrammarLanguage.GrammarId, *architectGrammarLanguage.Language, &architectGrammarLanguage)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update grammar language: %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated Architect Grammar Language %s", d.Id())
return readArchitectGrammarLanguage(ctx, d, meta)
}
// deleteArchitectGrammarLanguage is used by the architect_grammar_language resource to delete an architect grammar language from Genesys cloud.
func deleteArchitectGrammarLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarLanguageProxy(sdkConfig)
grammarId, languageCode := splitLanguageId(d.Id())
resp, err := proxy.deleteArchitectGrammarLanguage(ctx, grammarId, languageCode)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete grammar language %s: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getArchitectGrammarLanguageById(ctx, grammarId, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted Grammar Language %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("grammar Language %s still exists", d.Id()), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("grammar Language %s still exists", d.Id()), resp))
})
}
package architect_grammar_language
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util/architectlanguages"
)
/*
resource_genesyscloud_architect_grammar_language_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the architect_grammar_language resource.
3. The datasource schema definitions for the architect_grammar_language datasource.
4. The resource exporter configuration for the architect_grammar_language exporter.
*/
const resourceName = "genesyscloud_architect_grammar_language"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceArchitectGrammarLanguage())
regInstance.RegisterExporter(resourceName, ArchitectGrammarLanguageExporter())
}
// ResourceArchitectGrammarLanguage registers the genesyscloud_architect_grammar_language resource with Terraform
func ResourceArchitectGrammarLanguage() *schema.Resource {
fileMetadataResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`file_name`: {
Description: "The name of the file as defined by the user.",
Required: true,
Type: schema.TypeString,
},
`file_type`: {
Description: "The extension of the file.",
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"Gram", "Grxml"}, false),
},
"file_content_hash": {
Description: "Hash value of the file content. Used to detect changes.",
Type: schema.TypeString,
Required: true,
},
},
}
return &schema.Resource{
Description: `Genesys Cloud architect grammar language`,
CreateContext: provider.CreateWithPooledClient(createArchitectGrammarLanguage),
ReadContext: provider.ReadWithPooledClient(readArchitectGrammarLanguage),
UpdateContext: provider.UpdateWithPooledClient(updateArchitectGrammarLanguage),
DeleteContext: provider.DeleteWithPooledClient(deleteArchitectGrammarLanguage),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`grammar_id`: {
Description: "The id of the grammar this language belongs too. If this is changed a new language is created.",
Required: true,
Type: schema.TypeString,
ForceNew: true,
},
`language`: {
Description: "Language name. (eg. en-us). If this is changed a new language is created.",
Required: true,
Type: schema.TypeString,
ForceNew: true,
ValidateFunc: validation.StringInSlice(architectlanguages.Languages, false),
},
`voice_file_data`: {
Description: "Information about the associated voice file.",
Optional: true,
Type: schema.TypeList,
MaxItems: 1,
Elem: fileMetadataResource,
},
`dtmf_file_data`: {
Description: "Information about the associated dtmf file.",
Optional: true,
Type: schema.TypeList,
MaxItems: 1,
Elem: fileMetadataResource,
},
},
}
}
// ArchitectGrammarLanguageExporter returns the resourceExporter object used to hold the genesyscloud_architect_grammar_language exporter's config
func ArchitectGrammarLanguageExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthArchitectGrammarLanguage),
CustomFileWriter: resourceExporter.CustomFileWriterSettings{
RetrieveAndWriteFilesFunc: ArchitectGrammarLanguageResolver,
SubDirectory: "language_files",
},
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"grammar_id": {RefType: "genesyscloud_architect_grammar"},
},
}
}
package architect_grammar_language
import (
"context"
"fmt"
"log"
"os"
"path"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util/files"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_architect_grammar_language_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
// getArchitectGrammarLanguageFromResourceData maps data from schema ResourceData into a Genesys Cloud platformclientv2.Grammarlanguage
func getArchitectGrammarLanguageFromResourceData(d *schema.ResourceData) platformclientv2.Grammarlanguage {
grammarLanguage := platformclientv2.Grammarlanguage{
GrammarId: platformclientv2.String(d.Get("grammar_id").(string)),
Language: platformclientv2.String(d.Get("language").(string)),
}
if voiceFileDataList, ok := d.Get("voice_file_data").([]interface{}); ok {
grammarLanguage.VoiceFileMetadata = buildGrammarLanguageFileMetadata(voiceFileDataList)
}
if dtmfFileDataList, ok := d.Get("dtmf_file_data").([]interface{}); ok {
grammarLanguage.DtmfFileMetadata = buildGrammarLanguageFileMetadata(dtmfFileDataList)
}
return grammarLanguage
}
func buildGrammarLanguageFileMetadata(fileMetadata []interface{}) *platformclientv2.Grammarlanguagefilemetadata {
if fileMetadata == nil || len(fileMetadata) <= 0 {
return nil
}
var sdkMetadata platformclientv2.Grammarlanguagefilemetadata
metadataMap, ok := fileMetadata[0].(map[string]interface{})
if !ok {
return nil
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkMetadata.FileName, metadataMap, "file_name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkMetadata.FileType, metadataMap, "file_type")
// Get the current date time, helpful in the UI
currentTime := time.Now().UTC()
formattedTime := currentTime.Format("2006-01-02T15:04:05.999Z")
parsedTime, err := time.Parse("2006-01-02T15:04:05.999Z", formattedTime)
if err != nil {
log.Printf("Unable to get current date time %s", err)
} else {
sdkMetadata.DateUploaded = &parsedTime
}
return &sdkMetadata
}
func flattenGrammarLanguageFileMetadata(d *schema.ResourceData, fileMetadata *platformclientv2.Grammarlanguagefilemetadata, fileType FileType) []interface{} {
if fileMetadata == nil {
return nil
}
metadataMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(metadataMap, "file_name", fileMetadata.FileName)
resourcedata.SetMapValueIfNotNil(metadataMap, "file_type", fileMetadata.FileType)
if fileType == Voice {
if voiceData := d.Get("voice_file_data").([]interface{}); len(voiceData) > 0 {
voiceDataMap := voiceData[0].(map[string]interface{})
if hash, ok := voiceDataMap["file_content_hash"].(string); ok {
metadataMap["file_content_hash"] = hash
}
}
}
if fileType == Dtmf {
if dtmfData := d.Get("dtmf_file_data").([]interface{}); len(dtmfData) > 0 {
dtmfDataMap := dtmfData[0].(map[string]interface{})
if hash, ok := dtmfDataMap["file_content_hash"].(string); ok {
metadataMap["file_content_hash"] = hash
}
}
}
return []interface{}{metadataMap}
}
type grammarLanguageDownloader struct {
configMap map[string]interface{}
exportFilesFolderPath string
grammarId string
language *platformclientv2.Grammarlanguage
exportFileName string
subDirectory string
fileUrl string
fileExtension string
fileType FileType
}
func ArchitectGrammarLanguageResolver(languageId, exportDirectory, subDirectory string, configMap map[string]interface{}, meta interface{}) error {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectGrammarLanguageProxy(sdkConfig)
fullPath := path.Join(exportDirectory, subDirectory)
if err := os.MkdirAll(fullPath, os.ModePerm); err != nil {
return err
}
grammarId, languageCode := splitLanguageId(languageId)
language, _, err := proxy.getArchitectGrammarLanguageById(context.Background(), grammarId, languageCode)
if err != nil {
return err
}
downloader := grammarLanguageDownloader{
configMap: configMap,
exportFilesFolderPath: fullPath,
grammarId: grammarId,
language: language,
subDirectory: subDirectory,
}
return downloader.downloadVoiceAndDtmfFileData()
}
func (d *grammarLanguageDownloader) downloadVoiceAndDtmfFileData() error {
if err := d.downloadFileData(Voice); err != nil {
return err
}
return d.downloadFileData(Dtmf)
}
func (d *grammarLanguageDownloader) downloadFileData(fileType FileType) error {
var (
url *string
fileDataKey string
)
d.fileType = fileType
if d.fileType == Voice {
url = d.language.VoiceFileUrl
fileDataKey = "voice_file_data"
} else {
url = d.language.DtmfFileUrl
fileDataKey = "dtmf_file_data"
}
if url != nil {
if err := d.downloadLanguageFileAndUpdateConfigMap(*url); err != nil {
return fmt.Errorf("error downloading %s %s language file for grammar '%s': %v", fileDataKey, *d.language.Language, d.grammarId, err)
}
} else {
// If there are no files to download, we don't need this block in the export resource
d.configMap[fileDataKey] = nil
}
return nil
}
func (d *grammarLanguageDownloader) downloadLanguageFileAndUpdateConfigMap(url string) error {
d.fileUrl = url
d.setExportFileName()
if err := files.DownloadExportFile(d.exportFilesFolderPath, d.exportFileName, d.fileUrl); err != nil {
return err
}
d.updatePathsInExportConfigMap()
return nil
}
func (d *grammarLanguageDownloader) setExportFileName() {
d.setLanguageFileExtension()
fileTypeStr := "dtmf"
if d.fileType == Voice {
fileTypeStr = "voice"
}
d.exportFileName = fmt.Sprintf("%s-%s-%s.%s", *d.language.Language, fileTypeStr, d.grammarId, d.fileExtension)
}
func (d *grammarLanguageDownloader) setLanguageFileExtension() {
var fileExtension string
if d.fileType == Voice {
if d.language.VoiceFileMetadata != nil && d.language.VoiceFileMetadata.FileType != nil {
fileExtension = strings.ToLower(*d.language.VoiceFileMetadata.FileType)
}
} else {
if d.language.DtmfFileMetadata != nil && d.language.DtmfFileMetadata.FileType != nil {
fileExtension = strings.ToLower(*d.language.DtmfFileMetadata.FileType)
}
}
if fileExtension == "" {
log.Printf("no file type found when exporting grammar language '%s'. Defaulting to .grxml (grammar ID: '%s', language: '%s')", *d.language.Id, *d.language.GrammarId, *d.language.Language)
fileExtension = "grxml"
}
d.fileExtension = fileExtension
}
// updatePathsInExportConfigMap updates fields filename and file_content_hash to point to the files we downloaded to the export directory
func (d *grammarLanguageDownloader) updatePathsInExportConfigMap() {
var (
fileDataMapKey string
filePath = path.Join(d.subDirectory, d.exportFileName)
)
switch d.fileType {
case Voice:
fileDataMapKey = "voice_file_data"
default:
fileDataMapKey = "dtmf_file_data"
}
if fileDataList, ok := d.configMap[fileDataMapKey].([]interface{}); ok {
if fileDataMap, ok := fileDataList[0].(map[string]interface{}); ok {
fileDataMap["file_name"] = filePath
fileDataMap["file_content_hash"] = fmt.Sprintf(`${filesha256("%s")}`, filePath)
if fileDataMap["file_type"] == nil {
fileDataMap["file_type"] = ""
}
}
}
}
package architect_ivr
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
util "terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// dataSourceIvrRead retrieves the Genesys Cloud architect ivr id by name
func dataSourceIvrRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
ap := getArchitectIvrProxy(sdkConfig)
name := d.Get("name").(string)
// Query ivr by name. Retry in case search has not yet indexed the ivr.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
id, retryable, resp, err := ap.getArchitectIvrIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting IVR %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting IVR %s | error: %s", name, err), resp))
}
d.SetId(id)
return nil
})
}
package architect_ivr
import (
"context"
"fmt"
"log"
utillists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"time"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_architect_ivr_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *architectIvrProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createArchitectIvrFunc func(context.Context, *architectIvrProxy, platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error)
type getArchitectIvrFunc func(context.Context, *architectIvrProxy, string) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error)
type updateArchitectIvrFunc func(context.Context, *architectIvrProxy, string, platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error)
type deleteArchitectIvrFunc func(context.Context, *architectIvrProxy, string) (*platformclientv2.APIResponse, error)
type getAllArchitectIvrsFunc func(context.Context, *architectIvrProxy, string) (*[]platformclientv2.Ivr, *platformclientv2.APIResponse, error)
type getArchitectIvrIdByNameFunc func(context.Context, *architectIvrProxy, string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
// architectIvrProxy contains all methods that call genesys cloud APIs.
type architectIvrProxy struct {
clientConfig *platformclientv2.Configuration
api *platformclientv2.ArchitectApi
createArchitectIvrAttr createArchitectIvrFunc
getArchitectIvrAttr getArchitectIvrFunc
updateArchitectIvrAttr updateArchitectIvrFunc
deleteArchitectIvrAttr deleteArchitectIvrFunc
getAllArchitectIvrsAttr getAllArchitectIvrsFunc
getArchitectIvrIdByNameAttr getArchitectIvrIdByNameFunc
maxDnisPerRequest int
// functions to perform basic put/post request without chunking logic
updateArchitectIvrBasicAttr updateArchitectIvrFunc
createArchitectIvrBasicAttr createArchitectIvrFunc
}
// newArchitectIvrProxy initializes the proxy with all the data needed to communicate with Genesys Cloud
func newArchitectIvrProxy(clientConfig *platformclientv2.Configuration) *architectIvrProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &architectIvrProxy{
clientConfig: clientConfig,
api: api,
createArchitectIvrAttr: createArchitectIvrFn,
getArchitectIvrAttr: getArchitectIvrFn,
updateArchitectIvrAttr: updateArchitectIvrFn,
deleteArchitectIvrAttr: deleteArchitectIvrFn,
getAllArchitectIvrsAttr: getAllArchitectIvrsFn,
getArchitectIvrIdByNameAttr: getArchitectIvrIdByNameFn,
maxDnisPerRequest: maxDnisPerRequest,
createArchitectIvrBasicAttr: createArchitectIvrBasicFn,
updateArchitectIvrBasicAttr: updateArchitectIvrBasicFn,
}
}
// getArchitectIvrProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getArchitectIvrProxy(clientConfig *platformclientv2.Configuration) *architectIvrProxy {
if internalProxy == nil {
internalProxy = newArchitectIvrProxy(clientConfig)
}
return internalProxy
}
// getAllArchitectIvrs retrieves all Genesys Cloud Architect IVRs
func (a *architectIvrProxy) getAllArchitectIvrs(ctx context.Context, name string) (*[]platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.getAllArchitectIvrsAttr(ctx, a, name)
}
// getArchitectIvrIdByName retrieves a Genesys Cloud Architect IVR ID by name
func (a *architectIvrProxy) getArchitectIvrIdByName(ctx context.Context, name string) (string, bool, *platformclientv2.APIResponse, error) {
return a.getArchitectIvrIdByNameAttr(ctx, a, name)
}
// createArchitectIvr creates a Genesys Cloud Architect IVR
func (a *architectIvrProxy) createArchitectIvr(ctx context.Context, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.createArchitectIvrAttr(ctx, a, ivr)
}
// createArchitectIvr retrieves a Genesys Cloud Architect IVR by ID (implements chunking logic)
func (a *architectIvrProxy) getArchitectIvr(ctx context.Context, id string) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.getArchitectIvrAttr(ctx, a, id)
}
// updateArchitectIvr updates a Genesys Cloud Architect IVR (implements chunking logic)
func (a *architectIvrProxy) updateArchitectIvr(ctx context.Context, id string, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.updateArchitectIvrAttr(ctx, a, id, ivr)
}
// deleteArchitectIvr deletes a Genesys Cloud Architect IVR
func (a *architectIvrProxy) deleteArchitectIvr(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return a.deleteArchitectIvrAttr(ctx, a, id)
}
// createArchitectIvrBasicFn creates a Genesys Cloud Architect IVR (without chunking logic)
func (a *architectIvrProxy) createArchitectIvrBasic(ctx context.Context, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.createArchitectIvrBasicAttr(ctx, a, ivr)
}
// updateArchitectIvrBasic updates a Genesys Cloud Architect IVR (without chunking logic)
func (a *architectIvrProxy) updateArchitectIvrBasic(ctx context.Context, id string, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.updateArchitectIvrBasicAttr(ctx, a, id, ivr)
}
// createArchitectIvrFn is an implementation function for creating a Genesys Cloud Architect IVR
func createArchitectIvrFn(ctx context.Context, a *architectIvrProxy, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.uploadArchitectIvrWithChunkingLogic(ctx, true, "", ivr)
}
// getArchitectIvrFn is an implementation function for retrieving a Genesys Cloud Architect IVR by ID
func getArchitectIvrFn(_ context.Context, a *architectIvrProxy, id string) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.api.GetArchitectIvr(id)
}
// updateArchitectIvrFn is an implementation function for updating a Genesys Cloud Architect IVR
func updateArchitectIvrFn(ctx context.Context, a *architectIvrProxy, id string, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.uploadArchitectIvrWithChunkingLogic(ctx, false, id, ivr)
}
// createArchitectIvrBasicFn is an implementation function for performing a basic post of a Genesys Cloud Architect IVR
// without any chunking logic for the dnis field
func createArchitectIvrBasicFn(_ context.Context, a *architectIvrProxy, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.api.PostArchitectIvrs(ivr)
}
// updateArchitectIvrBasicFn is an implementation function for performing a basic put of a Genesys Cloud Architect IVR
// without any chunking logic for the dnis field
func updateArchitectIvrBasicFn(_ context.Context, a *architectIvrProxy, id string, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
return a.api.PutArchitectIvr(id, ivr)
}
// deleteArchitectIvrFn is an implementation function for deleting a Genesys Cloud Architect IVR
func deleteArchitectIvrFn(_ context.Context, a *architectIvrProxy, id string) (*platformclientv2.APIResponse, error) {
return a.api.DeleteArchitectIvr(id)
}
// getAllArchitectIvrsFn is an implementation function for retrieving all Genesys Cloud Architect IVRs
func getAllArchitectIvrsFn(_ context.Context, a *architectIvrProxy, name string) (*[]platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
var (
allIvrs []platformclientv2.Ivr
pageCount int
)
const pageSize = 100
ivrs, resp, err := a.api.GetArchitectIvrs(1, pageSize, "", "", name, "", "")
if err != nil {
return nil, resp, fmt.Errorf("error requesting page of architect ivrs: %v", err)
}
pageCount = *ivrs.PageCount
if ivrs.Entities != nil && len(*ivrs.Entities) > 0 {
allIvrs = append(allIvrs, *ivrs.Entities...)
}
if pageCount < 2 {
return &allIvrs, resp, nil
}
for pageNum := 2; pageNum <= pageCount; pageNum++ {
ivrs, resp, err := a.api.GetArchitectIvrs(pageNum, pageSize, "", "", name, "", "")
if err != nil {
return nil, resp, fmt.Errorf("error requesting page of architect ivrs: %v", err)
}
if ivrs.Entities == nil || len(*ivrs.Entities) == 0 {
break
}
allIvrs = append(allIvrs, *ivrs.Entities...)
}
return &allIvrs, resp, nil
}
// getArchitectIvrIdByNameFn is an implementation function for retrieving a Genesys Cloud Architect IVR ID by name
func getArchitectIvrIdByNameFn(ctx context.Context, a *architectIvrProxy, name string) (string, bool, *platformclientv2.APIResponse, error) {
ivrs, resp, err := getAllArchitectIvrsFn(ctx, a, name)
if err != nil {
return "", false, resp, fmt.Errorf("failed to read ivrs: %v", err)
}
if ivrs == nil || len(*ivrs) == 0 {
return "", true, resp, fmt.Errorf("failed to find ivr with name '%s': %v", name, err)
}
for _, ivr := range *ivrs {
if *ivr.Name == name {
return *ivr.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("failed to find ivr with name '%s': %v", name, err)
}
// uploadArchitectIvrWithChunkingLogic creates/updates an IVR. The function breaks the dnis field into chunks and uploads them in subsequent
// PUTs if the dnis array length is greater than a.maxDnisPerRequest
func (a *architectIvrProxy) uploadArchitectIvrWithChunkingLogic(ctx context.Context, post bool, id string, ivr platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
var (
respIvr *platformclientv2.Ivr
resp *platformclientv2.APIResponse
err error
dnisChunks [][]string
ivrBeforeUpdate *platformclientv2.Ivr
)
if ivr.Dnis != nil && len(*ivr.Dnis) > 0 {
ivr.Dnis, dnisChunks, resp, err = a.getIvrDnisAndChunks(ctx, id, post, &ivr)
if err != nil {
return nil, resp, err
}
}
if !post {
// Get copy of ivr before this update
log.Printf("Reading IVR %s to save copy of the configuration before attempting an update", id)
ivrBeforeUpdate, resp, err = a.getArchitectIvr(ctx, id)
if err != nil {
log.Printf("Failed to save a copy of IVR %s before starting chunking logic: %v", id, err)
}
}
// Perform initial post/put
if post {
respIvr, resp, err = a.createArchitectIvrBasic(ctx, ivr)
} else {
respIvr, resp, err = a.updateArchitectIvrBasic(ctx, id, ivr)
}
if err != nil {
operation := "update"
if post {
operation = "create"
}
return respIvr, resp, fmt.Errorf("error performing %s inside function uploadArchitectIvrWithChunkingLogic: %v", operation, err)
}
id = *respIvr.Id
// If there are chunks, call our function to perform each put request
if len(dnisChunks) > 0 {
respIvr, resp, err = a.uploadIvrDnisChunks(ctx, dnisChunks, respIvr)
if err != nil {
if resetResp, resetErr := a.resetArchitectIvrAfterFailedChunkUpload(ctx, post, id, ivrBeforeUpdate); resetErr != nil {
log.Print(resetErr, resetResp)
}
return respIvr, resp, err
}
}
return respIvr, resp, err
}
// uploadIvrDnisChunks loops through our chunks of dnis numbers and calls the uploadDnisChunk function for each.
func (a *architectIvrProxy) uploadIvrDnisChunks(ctx context.Context, dnisChunks [][]string, ivr *platformclientv2.Ivr) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
for i, chunk := range dnisChunks {
time.Sleep(2 * time.Second)
log.Printf("Uploading block %v of DID numbers to ivr config %s", i+1, *ivr.Id)
// upload current chunk to IVR
putIvr, resp, err := a.uploadDnisChunk(ctx, *ivr, chunk)
if err != nil {
return putIvr, resp, err
}
// Update ivr variable to represent the latest state of dnis field
ivr = putIvr
}
return ivr, nil, nil
}
// uploadDnisChunk takes an IVR object and a chunk of dnis numbers as parameters, appends the dnis numbers from the chunk to the
// dnis numbers on the IVR object, and performs a basic PUT request
func (a *architectIvrProxy) uploadDnisChunk(ctx context.Context, ivr platformclientv2.Ivr, chunk []string) (*platformclientv2.Ivr, *platformclientv2.APIResponse, error) {
var dnis []string
if ivr.Dnis != nil && len(*ivr.Dnis) > 0 {
dnis = append(dnis, *ivr.Dnis...)
}
dnis = append(dnis, chunk...)
ivr.Dnis = &dnis
log.Printf("Updating IVR config %s", *ivr.Id)
putIvr, resp, putErr := a.updateArchitectIvrBasic(ctx, *ivr.Id, ivr)
if putErr != nil {
return putIvr, resp, fmt.Errorf("error occured updating ivr %s in function uploadDnisChunk: %v", *ivr.Id, putErr)
}
return putIvr, resp, nil
}
// getIvrDnisAndChunks returns the dnis array to attach to the ivr on the initial POST/PUT
// along with the chunks to PUT after, if necessary.
func (a *architectIvrProxy) getIvrDnisAndChunks(ctx context.Context, id string, post bool, ivr *platformclientv2.Ivr) (*[]string, [][]string, *platformclientv2.APIResponse, error) {
var (
dnisChunks [][]string
dnisSliceForInitialUpload *[]string
)
// Create
if post {
dnisChunks = utillists.ChunkStringSlice(*ivr.Dnis, a.maxDnisPerRequest)
dnisSliceForInitialUpload = &dnisChunks[0]
dnisChunks = dnisChunks[1:] // all chunks after index 0, if they exist
return dnisSliceForInitialUpload, dnisChunks, nil, nil
}
// Update
// read the ivr to get current dnis array
currentIvr, resp, err := a.getArchitectIvr(ctx, id)
if err != nil {
return nil, nil, resp, err
}
// slice to establish what we're adding
dnisToAdd := utillists.SliceDifference(*ivr.Dnis, *currentIvr.Dnis)
// chunk that if necessary
if len(dnisToAdd) > a.maxDnisPerRequest {
dnisChunks = utillists.ChunkStringSlice(dnisToAdd, a.maxDnisPerRequest)
dnisForInitialCall := removeItemsToBeAddedFromOriginalList(*ivr.Dnis, dnisToAdd)
// append the first chunk
dnisForInitialCall = append(dnisForInitialCall, dnisChunks[0]...)
// return dnis for initial upload along with any chunks that may exist after index 0
return &dnisForInitialCall, dnisChunks[1:], resp, nil
}
// no chunking logic necessary
return ivr.Dnis, nil, resp, nil
}
// removeItemsToBeAddedFromOriginalList is used to remove the new did numbers from the dnis slice that we collected from the schema
// We do this because we are keeping the new numbers separate in the chunks slice to be uploaded subsequently
func removeItemsToBeAddedFromOriginalList(allDnis []string, toBeAdded []string) []string {
for _, number := range toBeAdded {
allDnis = utillists.Remove(allDnis, number)
}
return allDnis
}
// resetArchitectIvrAfterFailedChunkUpload
// If the chunking logic failed on create - delete the IVR that was created
// Otherwise the CreateContext func will fail and terraform will assume the IVR was never created
// If it failed on update - reset the ivr to its original state. This is done so that subsequent applies
// will output the same plan as before the last update failed
func (a *architectIvrProxy) resetArchitectIvrAfterFailedChunkUpload(ctx context.Context, post bool, id string, originalConfig *platformclientv2.Ivr) (resp *platformclientv2.APIResponse, err error) {
if post {
log.Printf("Deleting IVR %s because chunking logic failed on create", id)
if resp, err := a.deleteArchitectIvr(ctx, id); err != nil {
return resp, fmt.Errorf("failed to delete ivr %s after dnis chunking logic failed: %v", id, err)
}
log.Printf("Deleted IVR %s", id)
return nil, nil
}
if originalConfig == nil {
return nil, fmt.Errorf("cannot reset IVR %s without a copy of the original configuration", id)
}
originalConfig.Version = nil
log.Printf("Resetting IVR %s configuration after failed update", id)
if _, resp, err := a.updateArchitectIvrBasic(ctx, id, *originalConfig); err != nil {
return resp, fmt.Errorf("failed to reset ivr %s configuration: %v", id, err)
}
log.Printf("Reset IVR %s configuration", id)
return resp, nil
}
package architect_ivr
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// getAllIvrConfigs retrieves all architect IVRs and is used for the exporter
func getAllIvrConfigs(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
ap := getArchitectIvrProxy(clientConfig)
allIvrs, resp, err := ap.getAllArchitectIvrs(ctx, "")
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get archictect IVRs error: %s", err), resp)
}
for _, entity := range *allIvrs {
resources[*entity.Id] = &resourceExporter.ResourceMeta{Name: *entity.Name}
}
return resources, nil
}
// createIvrConfig is used by the resource to create a Genesys Cloud Architect IVR
func createIvrConfig(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectIvrProxy(sdkConfig)
ivrBody := buildArchitectIvrFromResourceData(d)
// It might need to wait for a dependent did_pool to be created to avoid an eventual consistency issue which
// would result in the error "Field 'didPoolId' is required and cannot be empty."
if ivrBody.Dnis != nil {
time.Sleep(3 * time.Second)
}
log.Printf("Creating IVR config %s", *ivrBody.Name)
ivrConfig, resp, err := ap.createArchitectIvr(ctx, *ivrBody)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create IVR config %s error: %s", *ivrBody.Name, err), resp)
}
d.SetId(*ivrConfig.Id)
log.Printf("Created IVR config %s %s", *ivrBody.Name, *ivrConfig.Id)
return readIvrConfig(ctx, d, meta)
}
// readIvrConfig is used by the resource to read a Genesys Cloud Architect IVR
func readIvrConfig(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectIvrProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectIvrConfig(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading IVR config %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
ivrConfig, resp, getErr := ap.getArchitectIvr(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read IVR config %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read IVR config %s | error: %s", d.Id(), getErr), resp))
}
if ivrConfig.State != nil && *ivrConfig.State == "deleted" {
d.SetId("")
return nil
}
_ = d.Set("name", *ivrConfig.Name)
_ = d.Set("dnis", lists.StringListToSetOrNil(ivrConfig.Dnis))
resourcedata.SetNillableValue(d, "description", ivrConfig.Description)
resourcedata.SetNillableReference(d, "open_hours_flow_id", ivrConfig.OpenHoursFlow)
resourcedata.SetNillableReference(d, "closed_hours_flow_id", ivrConfig.ClosedHoursFlow)
resourcedata.SetNillableReference(d, "holiday_hours_flow_id", ivrConfig.HolidayHoursFlow)
resourcedata.SetNillableReference(d, "schedule_group_id", ivrConfig.ScheduleGroup)
resourcedata.SetNillableReferenceWritableDivision(d, "division_id", ivrConfig.Division)
log.Printf("Read IVR config %s %s", d.Id(), *ivrConfig.Name)
return cc.CheckState(d)
})
}
// updateIvrConfig is used by the resource to update a Genesys Cloud Architect IVR
func updateIvrConfig(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectIvrProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current version
ivr, resp, getErr := ap.getArchitectIvr(ctx, d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read IVR config %s error: %s", d.Id(), getErr), resp)
}
ivrBody := buildArchitectIvrFromResourceData(d)
ivrBody.Version = ivr.Version
// It might need to wait for a dependent did_pool to be created to avoid an eventual consistency issue which
// would result in the error "Field 'didPoolId' is required and cannot be empty."
if ivrBody.Dnis != nil {
time.Sleep(3 * time.Second)
}
log.Printf("Updating IVR config %s", *ivrBody.Name)
_, resp, putErr := ap.updateArchitectIvr(ctx, d.Id(), *ivrBody)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update IVR config %s error: %s", d.Id(), putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated IVR config %s", d.Id())
return readIvrConfig(ctx, d, meta)
}
// deleteIvrConfig is used by the resource to delete a Genesys Cloud Architect IVR
func deleteIvrConfig(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ap := getArchitectIvrProxy(sdkConfig)
log.Printf("Deleting IVR config %s", name)
if resp, err := ap.deleteArchitectIvr(ctx, d.Id()); err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete IVR config %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
ivr, resp, err := ap.getArchitectIvr(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// IVR config deleted
log.Printf("Deleted IVR config %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting IVR config %s | error: %s", d.Id(), err), resp))
}
if ivr.State != nil && *ivr.State == "deleted" {
// IVR config deleted
log.Printf("Deleted IVR config with a deleted state %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("IVR config %s still exists", d.Id()), resp))
})
}
package architect_ivr
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
const (
resourceName = "genesyscloud_architect_ivr"
maxDnisPerRequest = 50
)
// SetRegistrar registers all resources, data sources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceArchitectIvr())
l.RegisterResource(resourceName, ResourceArchitectIvrConfig())
l.RegisterExporter(resourceName, ArchitectIvrExporter())
}
// ArchitectIvrExporter returns the resourceExporter object used to hold the genesyscloud_architect_ivr exporter's config
func ArchitectIvrExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIvrConfigs),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"open_hours_flow_id": {RefType: "genesyscloud_flow"},
"closed_hours_flow_id": {RefType: "genesyscloud_flow"},
"holiday_hours_flow_id": {RefType: "genesyscloud_flow"},
"schedule_group_id": {RefType: "genesyscloud_architect_schedulegroups"},
"division_id": {RefType: "genesyscloud_auth_division"},
},
}
}
// ResourceArchitectIvrConfig registers the genesyscloud_architect_ivr resource with Terraform
func ResourceArchitectIvrConfig() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud IVR config",
CreateContext: provider.CreateWithPooledClient(createIvrConfig),
ReadContext: provider.ReadWithPooledClient(readIvrConfig),
UpdateContext: provider.UpdateWithPooledClient(updateIvrConfig),
DeleteContext: provider.DeleteWithPooledClient(deleteIvrConfig),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the IVR config. Note: If the name changes, the existing Genesys Cloud IVR config will be dropped and recreated with a new ID. This can cause an Architect Flow to become invalid if the old flow is reference in the flow.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": {
Description: "IVR Config description.",
Type: schema.TypeString,
Optional: true,
},
"dnis": {
Description: fmt.Sprintf("The phone number(s) to contact the IVR by. Each phone number in the array must be in an E.164 number format. (Note: An array with a length greater than %v will be broken into chunks and uploaded in subsequent PUT requests.)", maxDnisPerRequest),
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString, ValidateDiagFunc: gcloud.ValidatePhoneNumber},
},
"open_hours_flow_id": {
Description: "ID of inbound call flow for open hours.",
Type: schema.TypeString,
Optional: true,
},
"closed_hours_flow_id": {
Description: "ID of inbound call flow for closed hours.",
Type: schema.TypeString,
Optional: true,
},
"holiday_hours_flow_id": {
Description: "ID of inbound call flow for holidays.",
Type: schema.TypeString,
Optional: true,
},
"schedule_group_id": {
Description: "Schedule group ID.",
Type: schema.TypeString,
Optional: true,
},
"division_id": {
Description: "Division ID.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
// DataSourceArchitectIvr registers the genesyscloud_architect_ivr data source
func DataSourceArchitectIvr() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud IVRs. Select an IVR by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceIvrRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "IVR name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package architect_ivr
import (
"fmt"
"strconv"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type IvrConfigStruct struct {
ResourceID string
Name string
Description string
Dnis []string
DependsOn string
DivisionId string
}
// GenerateIvrConfigResource returns an ivr resource as a string based on the IvrConfigStruct struct
func GenerateIvrConfigResource(ivrConfig *IvrConfigStruct) string {
var quotedDnsSlice []string
for _, val := range ivrConfig.Dnis {
quotedDnsSlice = append(quotedDnsSlice, strconv.Quote(val))
}
divisionId := ""
if ivrConfig.DivisionId != "" {
divisionId = ivrConfig.DivisionId
} else {
divisionId = "null"
}
return fmt.Sprintf(`resource "%s" "%s" {
name = "%s"
description = "%s"
dnis = [%s]
depends_on = [%s]
division_id = %s
}
`, resourceName,
ivrConfig.ResourceID,
ivrConfig.Name,
ivrConfig.Description,
strings.Join(quotedDnsSlice, ","),
ivrConfig.DependsOn,
divisionId,
)
}
// GenerateIvrDataSource generate an ivr data source as a string
func GenerateIvrDataSource(
resourceID string,
name string,
dependsOnResource string) string {
return fmt.Sprintf(`data "%s" "%s" {
name = %s
depends_on=[%s]
}
`, resourceName, resourceID, name, dependsOnResource)
}
func buildArchitectIvrFromResourceData(d *schema.ResourceData) *platformclientv2.Ivr {
ivrBody := platformclientv2.Ivr{
Name: platformclientv2.String(d.Get("name").(string)),
OpenHoursFlow: util.BuildSdkDomainEntityRef(d, "open_hours_flow_id"),
ClosedHoursFlow: util.BuildSdkDomainEntityRef(d, "closed_hours_flow_id"),
HolidayHoursFlow: util.BuildSdkDomainEntityRef(d, "holiday_hours_flow_id"),
ScheduleGroup: util.BuildSdkDomainEntityRef(d, "schedule_group_id"),
Dnis: lists.BuildSdkStringList(d, "dnis"),
}
if description := d.Get("description").(string); description != "" {
ivrBody.Description = &description
}
if divisionId := d.Get("division_id").(string); divisionId != "" {
ivrBody.Division = &platformclientv2.Writabledivision{Id: &divisionId}
}
return &ivrBody
}
package architect_schedulegroups
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_architect_schedulegroups.go contains the data source implementation
for the resource.
*/
// dataSourceArchitectSchedulegroupsRead retrieves by name the id in question
func dataSourceArchitectSchedulegroupsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newArchitectSchedulegroupsProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
scheduleGroupId, retryable, proxyResponse, err := proxy.getArchitectSchedulegroupsIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error searching architect schedulegroups %s | error: %s", name, err), proxyResponse))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no architect schedulegroups found with name %s", name), proxyResponse))
}
d.SetId(scheduleGroupId)
return nil
})
}
package architect_schedulegroups
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
)
/*
The genesyscloud_architect_schedulegroups_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *architectSchedulegroupsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createArchitectSchedulegroupsFunc func(ctx context.Context, p *architectSchedulegroupsProxy, scheduleGroup *platformclientv2.Schedulegroup) (*platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error)
type getAllArchitectSchedulegroupsFunc func(ctx context.Context, p *architectSchedulegroupsProxy) (*[]platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error)
type getArchitectSchedulegroupsIdByNameFunc func(ctx context.Context, p *architectSchedulegroupsProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getArchitectSchedulegroupsByIdFunc func(ctx context.Context, p *architectSchedulegroupsProxy, id string) (scheduleGroup *platformclientv2.Schedulegroup, response *platformclientv2.APIResponse, err error)
type updateArchitectSchedulegroupsFunc func(ctx context.Context, p *architectSchedulegroupsProxy, id string, scheduleGroup *platformclientv2.Schedulegroup) (*platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error)
type deleteArchitectSchedulegroupsFunc func(ctx context.Context, p *architectSchedulegroupsProxy, id string) (*platformclientv2.APIResponse, error)
// architectSchedulegroupsProxy contains all of the methods that call genesys cloud APIs.
type architectSchedulegroupsProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createArchitectSchedulegroupsAttr createArchitectSchedulegroupsFunc
getAllArchitectSchedulegroupsAttr getAllArchitectSchedulegroupsFunc
getArchitectSchedulegroupsIdByNameAttr getArchitectSchedulegroupsIdByNameFunc
getArchitectSchedulegroupsByIdAttr getArchitectSchedulegroupsByIdFunc
updateArchitectSchedulegroupsAttr updateArchitectSchedulegroupsFunc
deleteArchitectSchedulegroupsAttr deleteArchitectSchedulegroupsFunc
}
// newArchitectSchedulegroupsProxy initializes the architect schedulegroups proxy with all of the data needed to communicate with Genesys Cloud
func newArchitectSchedulegroupsProxy(clientConfig *platformclientv2.Configuration) *architectSchedulegroupsProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &architectSchedulegroupsProxy{
clientConfig: clientConfig,
architectApi: api,
createArchitectSchedulegroupsAttr: createArchitectSchedulegroupsFn,
getAllArchitectSchedulegroupsAttr: getAllArchitectSchedulegroupsFn,
getArchitectSchedulegroupsIdByNameAttr: getArchitectSchedulegroupsIdByNameFn,
getArchitectSchedulegroupsByIdAttr: getArchitectSchedulegroupsByIdFn,
updateArchitectSchedulegroupsAttr: updateArchitectSchedulegroupsFn,
deleteArchitectSchedulegroupsAttr: deleteArchitectSchedulegroupsFn,
}
}
// getArchitectSchedulegroupsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getArchitectSchedulegroupsProxy(clientConfig *platformclientv2.Configuration) *architectSchedulegroupsProxy {
if internalProxy == nil {
internalProxy = newArchitectSchedulegroupsProxy(clientConfig)
}
return internalProxy
}
// createArchitectSchedulegroups creates a Genesys Cloud architect schedulegroups
func (p *architectSchedulegroupsProxy) createArchitectSchedulegroups(ctx context.Context, architectSchedulegroups *platformclientv2.Schedulegroup) (*platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error) {
return p.createArchitectSchedulegroupsAttr(ctx, p, architectSchedulegroups)
}
// getArchitectSchedulegroups retrieves all Genesys Cloud architect schedulegroups
func (p *architectSchedulegroupsProxy) getAllArchitectSchedulegroups(ctx context.Context) (*[]platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error) {
return p.getAllArchitectSchedulegroupsAttr(ctx, p)
}
// getArchitectSchedulegroupsIdByName returns a single Genesys Cloud architect schedulegroups by a name
func (p *architectSchedulegroupsProxy) getArchitectSchedulegroupsIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getArchitectSchedulegroupsIdByNameAttr(ctx, p, name)
}
// getArchitectSchedulegroupsById returns a single Genesys Cloud architect schedulegroups by Id
func (p *architectSchedulegroupsProxy) getArchitectSchedulegroupsById(ctx context.Context, id string) (architectSchedulegroups *platformclientv2.Schedulegroup, response *platformclientv2.APIResponse, err error) {
return p.getArchitectSchedulegroupsByIdAttr(ctx, p, id)
}
// updateArchitectSchedulegroups updates a Genesys Cloud architect schedulegroups
func (p *architectSchedulegroupsProxy) updateArchitectSchedulegroups(ctx context.Context, id string, architectSchedulegroups *platformclientv2.Schedulegroup) (*platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error) {
return p.updateArchitectSchedulegroupsAttr(ctx, p, id, architectSchedulegroups)
}
// deleteArchitectSchedulegroups deletes a Genesys Cloud architect schedulegroups by Id
func (p *architectSchedulegroupsProxy) deleteArchitectSchedulegroups(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return p.deleteArchitectSchedulegroupsAttr(ctx, p, id)
}
// createArchitectSchedulegroupsFn is an implementation function for creating a Genesys Cloud architect schedulegroups
func createArchitectSchedulegroupsFn(ctx context.Context, p *architectSchedulegroupsProxy, architectSchedulegroups *platformclientv2.Schedulegroup) (*platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error) {
scheduleGroup, apiResponse, err := p.architectApi.PostArchitectSchedulegroups(*architectSchedulegroups)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to create architect schedulegroups: %s", err)
}
return scheduleGroup, apiResponse, nil
}
// getAllArchitectSchedulegroupsFn is the implementation for retrieving all architect schedulegroups in Genesys Cloud
func getAllArchitectSchedulegroupsFn(ctx context.Context, p *architectSchedulegroupsProxy) (*[]platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error) {
var allScheduleGroups []platformclientv2.Schedulegroup
const pageSize = 100
scheduleGroups, apiResponse, err := p.architectApi.GetArchitectSchedulegroups(1, pageSize, "", "", "", "", nil)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to get schedule group: %v", err)
}
if scheduleGroups.Entities == nil || len(*scheduleGroups.Entities) == 0 {
return &allScheduleGroups, apiResponse, nil
}
for _, scheduleGroup := range *scheduleGroups.Entities {
allScheduleGroups = append(allScheduleGroups, scheduleGroup)
}
for pageNum := 2; pageNum <= *scheduleGroups.PageCount; pageNum++ {
scheduleGroups, apiResponse, err := p.architectApi.GetArchitectSchedulegroups(pageNum, pageSize, "", "", "", "", nil)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to get schedule group: %v", err)
}
if scheduleGroups.Entities == nil || len(*scheduleGroups.Entities) == 0 {
break
}
for _, scheduleGroup := range *scheduleGroups.Entities {
allScheduleGroups = append(allScheduleGroups, scheduleGroup)
}
}
return &allScheduleGroups, apiResponse, nil
}
// getArchitectSchedulegroupsIdByNameFn is an implementation of the function to get a Genesys Cloud architect schedulegroups by name
func getArchitectSchedulegroupsIdByNameFn(ctx context.Context, p *architectSchedulegroupsProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
scheduleGroups, apiResponse, err := getAllArchitectSchedulegroupsFn(ctx, p)
if err != nil {
return "", false, apiResponse, err
}
if scheduleGroups == nil || len(*scheduleGroups) == 0 {
return "", true, apiResponse, fmt.Errorf("No architect schedulegroups found with name %s", name)
}
for _, scheduleGroup := range *scheduleGroups {
if *scheduleGroup.Name == name {
log.Printf("Retrieved the architect schedulegroups id %s by name %s", *scheduleGroup.Id, name)
return *scheduleGroup.Id, false, apiResponse, nil
}
}
return "", true, apiResponse, fmt.Errorf("Unable to find architect schedulegroups with name %s", name)
}
// getArchitectSchedulegroupsByIdFn is an implementation of the function to get a Genesys Cloud architect schedulegroups by Id
func getArchitectSchedulegroupsByIdFn(ctx context.Context, p *architectSchedulegroupsProxy, id string) (architectSchedulegroups *platformclientv2.Schedulegroup, response *platformclientv2.APIResponse, err error) {
scheduleGroup, apiResponse, err := p.architectApi.GetArchitectSchedulegroup(id)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to retrieve architect schedulegroups by id %s: %s", id, err)
}
return scheduleGroup, apiResponse, nil
}
// updateArchitectSchedulegroupsFn is an implementation of the function to update a Genesys Cloud architect schedulegroups
func updateArchitectSchedulegroupsFn(ctx context.Context, p *architectSchedulegroupsProxy, id string, architectSchedulegroups *platformclientv2.Schedulegroup) (*platformclientv2.Schedulegroup, *platformclientv2.APIResponse, error) {
group, apiResponse, err := getArchitectSchedulegroupsByIdFn(ctx, p, id)
if err != nil {
return nil, apiResponse, fmt.Errorf("failed to get schedule group %s by id: %s", id, err)
}
architectSchedulegroups.Version = group.Version
scheduleGroup, apiResponse, err := p.architectApi.PutArchitectSchedulegroup(id, *architectSchedulegroups)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to update architect schedulegroups: %s", err)
}
return scheduleGroup, apiResponse, nil
}
// deleteArchitectSchedulegroupsFn is an implementation function for deleting a Genesys Cloud architect schedulegroups
func deleteArchitectSchedulegroupsFn(ctx context.Context, p *architectSchedulegroupsProxy, id string) (*platformclientv2.APIResponse, error) {
resp, err := p.architectApi.DeleteArchitectSchedulegroup(id)
if err != nil {
return resp, fmt.Errorf("Failed to delete architect schedulegroups: %s", err)
}
return resp, nil
}
package architect_schedulegroups
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_architect_schedulegroups.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthArchitectSchedulegroups retrieves all of the architect schedulegroups via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthArchitectSchedulegroups(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getArchitectSchedulegroupsProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
scheduleGroups, proxyResponse, err := proxy.getAllArchitectSchedulegroups(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of schedule groups error: %s", err), proxyResponse)
}
for _, scheduleGroup := range *scheduleGroups {
resources[*scheduleGroup.Id] = &resourceExporter.ResourceMeta{Name: *scheduleGroup.Name}
}
return resources, nil
}
// createArchitectSchedulegroups is used by the architect_schedulegroups resource to create Genesys cloud architect schedulegroups
func createArchitectSchedulegroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectSchedulegroupsProxy(sdkConfig)
schedGroup := getArchitectScheduleGroupFromResourceData(d)
log.Printf("Creating schedule group %s", *schedGroup.Name)
scheduleGroup, proxyResponse, err := proxy.createArchitectSchedulegroups(ctx, &schedGroup)
if err != nil {
msg := ""
if strings.Contains(fmt.Sprintf("%v", err), "routing:schedule:add") {
msg = "\nYou must have all divisions and future divisions selected in your OAuth client role"
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create schedule group %s | Error: %s %s", d.Id(), err, msg), proxyResponse)
}
d.SetId(*scheduleGroup.Id)
log.Printf("Created schedule group %s %s", *schedGroup.Name, *scheduleGroup.Id)
return readArchitectSchedulegroups(ctx, d, meta)
}
// readArchitectSchedulegroups is used by the architect_schedulegroups resource to read an architect schedulegroups from genesys cloud
func readArchitectSchedulegroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectSchedulegroupsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectSchedulegroups(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading schedule group %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
scheduleGroup, proxyResponse, getErr := proxy.getArchitectSchedulegroupsById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(proxyResponse) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read schedule group %s | error: %s", d.Id(), getErr), proxyResponse))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read schedule group %s | error: %s", d.Id(), getErr), proxyResponse))
}
d.Set("name", *scheduleGroup.Name)
d.Set("division_id", *scheduleGroup.Division.Id)
d.Set("description", nil)
if scheduleGroup.Description != nil {
d.Set("description", *scheduleGroup.Description)
}
d.Set("time_zone", nil)
if scheduleGroup.TimeZone != nil {
d.Set("time_zone", *scheduleGroup.TimeZone)
}
if scheduleGroup.OpenSchedules != nil {
d.Set("open_schedules_id", util.SdkDomainEntityRefArrToSet(*scheduleGroup.OpenSchedules))
} else {
d.Set("open_schedules_id", nil)
}
if scheduleGroup.ClosedSchedules != nil {
d.Set("closed_schedules_id", util.SdkDomainEntityRefArrToSet(*scheduleGroup.ClosedSchedules))
} else {
d.Set("closed_schedules_id", nil)
}
if scheduleGroup.HolidaySchedules != nil {
d.Set("holiday_schedules_id", util.SdkDomainEntityRefArrToSet(*scheduleGroup.HolidaySchedules))
} else {
d.Set("holiday_schedules_id", nil)
}
log.Printf("Read schedule group %s %s", d.Id(), *scheduleGroup.Name)
return cc.CheckState(d)
})
}
// updateArchitectSchedulegroups is used by the architect_schedulegroups resource to update an architect schedulegroups in Genesys Cloud
func updateArchitectSchedulegroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectSchedulegroupsProxy(sdkConfig)
scheduleGroup := getArchitectScheduleGroupFromResourceData(d)
log.Printf("Updating schedule group %s %s", *scheduleGroup.Name, d.Id())
_, proxyResponse, err := proxy.updateArchitectSchedulegroups(ctx, d.Id(), &scheduleGroup)
if err != nil {
msg := ""
if strings.Contains(fmt.Sprintf("%v", err), "routing:schedule:add") {
msg = "\nYou must have all divisions and future divisions selected in your OAuth client role"
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update schedule group %s | Error: %s %s", d.Id(), err, msg), proxyResponse)
}
log.Printf("Updated schedule group %s %s", *scheduleGroup.Name, d.Id())
return readArchitectSchedulegroups(ctx, d, meta)
}
// deleteArchitectSchedulegroups is used by the architect_schedulegroups resource to delete an architect schedulegroups from Genesys cloud
func deleteArchitectSchedulegroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectSchedulegroupsProxy(sdkConfig)
// DEVTOOLING-313: a schedule group linked to an IVR will not be able to be deleted until that IVR is deleted. Retrying here to make sure it is cleared properly.
log.Printf("Deleting schedule group %s", d.Id())
diagErr := util.RetryWhen(util.IsStatus409, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting schedule group %s", d.Id())
proxyResponse, err := proxy.deleteArchitectSchedulegroups(ctx, d.Id())
if err != nil {
return proxyResponse, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete schedule group %s error: %s", d.Id(), err), proxyResponse)
}
return proxyResponse, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
scheduleGroup, proxyResponse, err := proxy.getArchitectSchedulegroupsById(ctx, d.Id())
if err != nil {
if util.IsStatus404(proxyResponse) {
// schedule group deleted
log.Printf("Deleted schedule group %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting schedule group %s | error: %s", d.Id(), err), proxyResponse))
}
if scheduleGroup.State != nil && *scheduleGroup.State == "deleted" {
// schedule group deleted
log.Printf("Deleted schedule group %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Schedule group %s still exists", d.Id()), proxyResponse))
})
}
func getArchitectScheduleGroupFromResourceData(d *schema.ResourceData) platformclientv2.Schedulegroup {
scheduleGroup := platformclientv2.Schedulegroup{
Name: platformclientv2.String(d.Get("name").(string)),
OpenSchedules: util.BuildSdkDomainEntityRefArr(d, "open_schedules_id"),
ClosedSchedules: util.BuildSdkDomainEntityRefArr(d, "closed_schedules_id"),
HolidaySchedules: util.BuildSdkDomainEntityRefArr(d, "holiday_schedules_id"),
}
divisionID := d.Get("division_id").(string)
description := d.Get("description").(string)
timeZone := d.Get("time_zone").(string)
// Optional attributes
if divisionID != "" {
scheduleGroup.Division = &platformclientv2.Writabledivision{Id: &divisionID}
}
if description != "" {
scheduleGroup.Description = &description
}
if timeZone != "" {
scheduleGroup.TimeZone = &timeZone
}
return scheduleGroup
}
package architect_schedulegroups
import (
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
resource_genesycloud_architect_schedulegroups_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the architect_schedulegroups resource.
3. The datasource schema definitions for the architect_schedulegroups datasource.
4. The resource exporter configuration for the architect_schedulegroups exporter.
*/
const resourceName = "genesyscloud_architect_schedulegroups"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceArchitectSchedulegroups())
regInstance.RegisterDataSource(resourceName, DataSourceArchitectSchedulegroups())
regInstance.RegisterExporter(resourceName, ArchitectSchedulegroupsExporter())
}
// ResourceArchitectSchedulegroups registers the genesyscloud_architect_schedulegroups resource with Terraform
func ResourceArchitectSchedulegroups() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Architect Schedule Groups",
CreateContext: provider.CreateWithPooledClient(createArchitectSchedulegroups),
ReadContext: provider.ReadWithPooledClient(readArchitectSchedulegroups),
UpdateContext: provider.UpdateWithPooledClient(updateArchitectSchedulegroups),
DeleteContext: provider.DeleteWithPooledClient(deleteArchitectSchedulegroups),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the schedule group.",
Type: schema.TypeString,
Required: true,
},
"division_id": {
Description: "The division to which this schedule group will belong. If not set, the home division will be used. If set, you must have all divisions and future divisions selected in your OAuth client role",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Description: "Description of the schedule group.",
Type: schema.TypeString,
Optional: true,
},
"time_zone": {
Description: "The timezone the schedules are a part of.",
Type: schema.TypeString,
Optional: true,
},
"open_schedules_id": {
Description: "The schedules defining the hours an organization is open.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"closed_schedules_id": {
Description: "The schedules defining the hours an organization is closed.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"holiday_schedules_id": {
Description: "The schedules defining the hours an organization is closed for the holidays.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
// ArchitectSchedulegroupsExporter returns the resourceExporter object used to hold the genesyscloud_architect_schedulegroups exporter's config
func ArchitectSchedulegroupsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthArchitectSchedulegroups),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
"open_schedules_id": {RefType: "genesyscloud_architect_schedules"},
"closed_schedules_id": {RefType: "genesyscloud_architect_schedules"},
"holiday_schedules_id": {RefType: "genesyscloud_architect_schedules"},
},
}
}
// DataSourceArchitectSchedulegroups registers the genesyscloud_architect_schedulegroups data source
func DataSourceArchitectSchedulegroups() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Schedule Groups. Select a schedule group by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceArchitectSchedulegroupsRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: "Schedule Group name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package architect_user_prompt
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceUserPromptRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectUserPromptProxy(sdkConfig)
name := d.Get("name").(string)
// Query user prompt by name. Retry in case search has not yet indexed the user prompt.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
promptId, resp, getErr, retryable := proxy.getArchitectUserPromptIdByName(ctx, name)
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting user prompt by name %s | error: %s", name, getErr), resp))
}
if getErr != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error making user prompt request: %s", getErr), resp))
}
d.SetId(promptId)
return nil
})
}
package architect_user_prompt
import (
"context"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *architectUserPromptProxy
type createArchitectUserPromptFunc func(ctx context.Context, p *architectUserPromptProxy, body platformclientv2.Prompt) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error)
type getArchitectUserPromptFunc func(ctx context.Context, p *architectUserPromptProxy, id string, includeMediaUris bool, includeResources bool, language []string) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error, bool)
type getAllArchitectUserPromptsFunc func(ctx context.Context, p *architectUserPromptProxy, includeMediaUris bool, includeResources bool, name string) (*[]platformclientv2.Prompt, *platformclientv2.APIResponse, error, bool)
type updateArchitectUserPromptFunc func(ctx context.Context, p *architectUserPromptProxy, id string, body platformclientv2.Prompt) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error)
type deleteArchitectUserPromptFunc func(ctx context.Context, p *architectUserPromptProxy, id string, allResources bool) (*platformclientv2.APIResponse, error)
type createArchitectUserPromptResourceFunc func(ctx context.Context, p *architectUserPromptProxy, id string, body platformclientv2.Promptassetcreate) (*platformclientv2.Promptasset, *platformclientv2.APIResponse, error)
type updateArchitectUserPromptResourceFunc func(ctx context.Context, p *architectUserPromptProxy, id string, languageCode string, body platformclientv2.Promptasset) (*platformclientv2.Promptasset, *platformclientv2.APIResponse, error)
type getArchitectUserPromptIdByNameFunc func(ctx context.Context, p *architectUserPromptProxy, name string) (string, *platformclientv2.APIResponse, error, bool)
// ArchitectUserPromptProxy - proxy for Architect User Prompts
type architectUserPromptProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createArchitectUserPromptAttr createArchitectUserPromptFunc
getArchitectUserPromptAttr getArchitectUserPromptFunc
getAllArchitectUserPromptsAttr getAllArchitectUserPromptsFunc
updateArchitectUserPromptAttr updateArchitectUserPromptFunc
deleteArchitectUserPromptAttr deleteArchitectUserPromptFunc
createArchitectUserPromptResourceAttr createArchitectUserPromptResourceFunc
updateArchitectUserPromptResourceAttr updateArchitectUserPromptResourceFunc
getArchitectUserPromptIdByNameAttr getArchitectUserPromptIdByNameFunc
}
func newArchitectUserPromptProxy(clientConfig *platformclientv2.Configuration) *architectUserPromptProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &architectUserPromptProxy{
clientConfig: clientConfig,
architectApi: api,
createArchitectUserPromptAttr: createArchitectUserPromptFn,
getArchitectUserPromptAttr: getArchitectUserPromptFn,
getAllArchitectUserPromptsAttr: getAllArchitectUserPromptsFn,
updateArchitectUserPromptAttr: updateArchitectUserPromptFn,
deleteArchitectUserPromptAttr: deleteArchitectUserPromptFn,
createArchitectUserPromptResourceAttr: createArchitectUserPromptResourceFn,
updateArchitectUserPromptResourceAttr: updateArchitectUserPromptResourceFn,
getArchitectUserPromptIdByNameAttr: getArchitectUserPromptIdByNameFn,
}
}
func getArchitectUserPromptProxy(clientConfig *platformclientv2.Configuration) *architectUserPromptProxy {
if internalProxy == nil {
internalProxy = newArchitectUserPromptProxy(clientConfig)
}
return internalProxy
}
// createArchitectUserPrompt creates a new user prompt
func (p *architectUserPromptProxy) createArchitectUserPrompt(ctx context.Context, body platformclientv2.Prompt) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error) {
return p.createArchitectUserPromptAttr(ctx, p, body)
}
// getArchitectUserPrompt retrieves a user prompt
func (p *architectUserPromptProxy) getArchitectUserPrompt(ctx context.Context, id string, includeMediaUris bool, includeResources bool, language []string) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error, bool) {
return p.getArchitectUserPromptAttr(ctx, p, id, includeMediaUris, includeResources, language)
}
// getAllArchitectUserPrompts retrieves a list of user prompts
func (p *architectUserPromptProxy) getAllArchitectUserPrompts(ctx context.Context, includeMediaUris bool, includeResources bool, name string) (*[]platformclientv2.Prompt, *platformclientv2.APIResponse, error, bool) {
return p.getAllArchitectUserPromptsAttr(ctx, p, includeMediaUris, includeResources, name)
}
// updateArchitectUserPrompt updates a user prompt
func (p *architectUserPromptProxy) updateArchitectUserPrompt(ctx context.Context, id string, body platformclientv2.Prompt) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error) {
return p.updateArchitectUserPromptAttr(ctx, p, id, body)
}
// deleteArchitectUserPrompt deletes a user prompt
func (p *architectUserPromptProxy) deleteArchitectUserPrompt(ctx context.Context, id string, allResources bool) (*platformclientv2.APIResponse, error) {
return p.deleteArchitectUserPromptAttr(ctx, p, id, allResources)
}
// createArchitectUserPromptResource creates a new user prompt resource
func (p *architectUserPromptProxy) createArchitectUserPromptResource(ctx context.Context, id string, body platformclientv2.Promptassetcreate) (*platformclientv2.Promptasset, *platformclientv2.APIResponse, error) {
return p.createArchitectUserPromptResourceAttr(ctx, p, id, body)
}
// updateArchitectUserPromptResource updates a user prompt resource
func (p *architectUserPromptProxy) updateArchitectUserPromptResource(ctx context.Context, id string, languageCode string, body platformclientv2.Promptasset) (*platformclientv2.Promptasset, *platformclientv2.APIResponse, error) {
return p.updateArchitectUserPromptResourceAttr(ctx, p, id, languageCode, body)
}
// getArchitectUserPromptIdByName retrieves a user prompt by name
func (p *architectUserPromptProxy) getArchitectUserPromptIdByName(ctx context.Context, name string) (string, *platformclientv2.APIResponse, error, bool) {
return p.getArchitectUserPromptIdByNameAttr(ctx, p, name)
}
func createArchitectUserPromptFn(ctx context.Context, p *architectUserPromptProxy, body platformclientv2.Prompt) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error) {
prompt, response, err := p.architectApi.PostArchitectPrompts(body)
if err != nil {
return nil, response, err
}
return prompt, response, nil
}
func getArchitectUserPromptFn(ctx context.Context, p *architectUserPromptProxy, id string, includeMediaUris bool, includeResources bool, language []string) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error, bool) {
prompt, response, err := p.architectApi.GetArchitectPrompt(id, includeMediaUris, includeResources, language)
if err != nil {
return nil, response, err, true
}
return prompt, response, nil, false
}
func updateArchitectUserPromptFn(ctx context.Context, p *architectUserPromptProxy, id string, body platformclientv2.Prompt) (*platformclientv2.Prompt, *platformclientv2.APIResponse, error) {
prompt, response, err := p.architectApi.PutArchitectPrompt(id, body)
if err != nil {
return nil, response, err
}
return prompt, response, nil
}
func deleteArchitectUserPromptFn(ctx context.Context, p *architectUserPromptProxy, id string, allResources bool) (*platformclientv2.APIResponse, error) {
response, err := p.architectApi.DeleteArchitectPrompt(id, allResources)
if err != nil {
return response, err
}
return response, nil
}
func getAllArchitectUserPromptsFn(ctx context.Context, p *architectUserPromptProxy, includeMediaUris bool, includeResources bool, name string) (*[]platformclientv2.Prompt, *platformclientv2.APIResponse, error, bool) {
var (
pageCount int
pageNum = 1
allPrompts []platformclientv2.Prompt
nameString []string
)
if len(name) == 0 {
nameString = nil
} else {
nameString = append(nameString, name)
}
const pageSize = 100
userPrompts, response, err := p.architectApi.GetArchitectPrompts(pageNum, pageSize, nameString, "", "", "", "", includeMediaUris, includeResources, nil)
if err != nil {
return nil, response, err, true
}
if userPrompts != nil && userPrompts.Entities != nil && len(*userPrompts.Entities) > 0 {
allPrompts = append(allPrompts, *userPrompts.Entities...)
}
pageCount = *userPrompts.PageCount
for pageNum := 2; pageNum <= pageCount; pageNum++ {
userPrompts, response, getErr := p.architectApi.GetArchitectPrompts(pageNum, pageSize, nameString, "", "", "", "", includeMediaUris, includeResources, nil)
if getErr != nil {
return nil, response, getErr, true
}
if userPrompts == nil || userPrompts.Entities == nil || len(*userPrompts.Entities) == 0 {
break
}
allPrompts = append(allPrompts, *userPrompts.Entities...)
}
return &allPrompts, response, nil, false
}
func createArchitectUserPromptResourceFn(ctx context.Context, p *architectUserPromptProxy, id string, body platformclientv2.Promptassetcreate) (*platformclientv2.Promptasset, *platformclientv2.APIResponse, error) {
promptAsset, response, err := p.architectApi.PostArchitectPromptResources(id, body)
if err != nil {
return nil, response, err
}
return promptAsset, response, nil
}
func updateArchitectUserPromptResourceFn(ctx context.Context, p *architectUserPromptProxy, id string, languageCode string, body platformclientv2.Promptasset) (*platformclientv2.Promptasset, *platformclientv2.APIResponse, error) {
promptAsset, response, err := p.architectApi.PutArchitectPromptResource(id, languageCode, body)
if err != nil {
return nil, response, err
}
return promptAsset, response, nil
}
func getArchitectUserPromptIdByNameFn(ctx context.Context, p *architectUserPromptProxy, name string) (string, *platformclientv2.APIResponse, error, bool) {
// Query user prompt by name. Retry in case search has not yet indexed the user prompt.
prompts, response, err, retryable := p.getAllArchitectUserPrompts(ctx, true, true, name)
if err != nil {
return "", response, err, true
}
if prompts == nil {
return "", response, err, true
}
for _, prompt := range *prompts {
if name == *prompt.Name {
log.Printf("found user prompt id %s by name %s", *prompt.Id, *prompt.Name)
return *prompt.Id, response, nil, retryable
}
}
return "", response, err, true
}
package architect_user_prompt
import (
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
architectlanguages "terraform-provider-genesyscloud/genesyscloud/util/architectlanguages"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
const resourceName = "genesyscloud_architect_user_prompt"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceArchitectUserPrompt())
regInstance.RegisterDataSource(resourceName, DataSourceUserPrompt())
regInstance.RegisterExporter(resourceName, ArchitectUserPromptExporter())
}
func ArchitectUserPromptExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllUserPrompts),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
CustomFileWriter: resourceExporter.CustomFileWriterSettings{
RetrieveAndWriteFilesFunc: ArchitectPromptAudioResolver,
SubDirectory: "audio_prompts",
},
}
}
func DataSourceUserPrompt() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud User Prompts. Select a user prompt by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceUserPromptRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "User Prompt name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
var userPromptResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"language": {
Description: "Language for the prompt resource. (eg. en-us)",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(architectlanguages.Languages, false),
},
"tts_string": {
Description: "Text to Speech (TTS) value for the prompt.",
Type: schema.TypeString,
Optional: true,
},
"text": {
Description: "Text value for the prompt.",
Type: schema.TypeString,
Optional: true,
},
"filename": {
Description: "Path or URL to the file to be uploaded as prompt.",
Type: schema.TypeString,
Optional: true,
},
"file_content_hash": {
Description: "Hash value of the audio file content. Used to detect changes. Only required when uploading a local audio file.",
Type: schema.TypeString,
Optional: true,
},
},
}
func ResourceArchitectUserPrompt() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud User Audio Prompt",
CreateContext: provider.CreateWithPooledClient(createUserPrompt),
ReadContext: provider.ReadWithPooledClient(readUserPrompt),
UpdateContext: provider.UpdateWithPooledClient(updateUserPrompt),
DeleteContext: provider.DeleteWithPooledClient(deleteUserPrompt),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the user audio prompt. Note: If the name of the user prompt is changed, this will cause the Prompt to be dropped and recreated with a new ID. This will generate a new ID for the prompt and will invalidate any Architect flows referencing it. ",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": {
Description: "Description of the user audio prompt.",
Type: schema.TypeString,
Optional: true,
},
"resources": {
Description: "Audio of TTS resources for the audio prompt.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: userPromptResource,
},
},
}
}
package architect_user_prompt
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllUserPrompts(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getArchitectUserPromptProxy(clientConfig)
userPrompts, resp, err, _ := proxy.getAllArchitectUserPrompts(ctx, false, false, "")
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to get user prompts: %s", err), resp)
}
for _, userPrompt := range *userPrompts {
resources[*userPrompt.Id] = &resourceExporter.ResourceMeta{Name: *userPrompt.Name}
}
return resources, nil
}
func createUserPrompt(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectUserPromptProxy(sdkConfig)
prompt := platformclientv2.Prompt{
Name: &name,
}
if description != "" {
prompt.Description = &description
}
log.Printf("Creating user prompt %s", name)
userPrompt, resp, err := proxy.createArchitectUserPrompt(ctx, prompt)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create user prompt %s: %s", name, err), resp)
}
// Create the prompt resources
if resources, ok := d.GetOk("resources"); ok && resources != nil {
promptResources := resources.(*schema.Set).List()
for _, promptResource := range promptResources {
resourceMap := promptResource.(map[string]interface{})
resourceLanguage := resourceMap["language"].(string)
tag := make(map[string][]string)
resourceFilenameStr := ""
if filename, ok := resourceMap["filename"].(string); ok && filename != "" {
tag["filename"] = []string{filename}
resourceFilenameStr = filename
}
promptResource := platformclientv2.Promptassetcreate{
Language: &resourceLanguage,
Tags: &tag,
}
if resourceTtsString, ok := resourceMap["tts_string"].(string); ok && resourceTtsString != "" {
promptResource.TtsString = &resourceTtsString
}
if resourceText, ok := resourceMap["text"].(string); ok && resourceText != "" {
promptResource.Text = &resourceText
}
log.Printf("Creating user prompt resource for language: %s", resourceLanguage)
userPromptResource, resp, err := proxy.createArchitectUserPromptResource(ctx, *userPrompt.Id, promptResource)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create user prompt resource %s: %s", name, err), resp)
}
uploadUri := userPromptResource.UploadUri
if resourceFilenameStr == "" {
continue
}
if err := uploadPrompt(uploadUri, &resourceFilenameStr, sdkConfig); err != nil {
d.SetId(*userPrompt.Id)
diagErr := deleteUserPrompt(ctx, d, meta)
if diagErr != nil {
log.Printf("Error deleting user prompt resource %s: %v", *userPrompt.Id, diagErr)
}
d.SetId("")
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to upload user prompt resource %s", name), err)
}
log.Printf("Successfully uploaded user prompt resource for language: %s", resourceLanguage)
}
}
d.SetId(*userPrompt.Id)
log.Printf("Created user prompt %s %s", name, *userPrompt.Id)
return readUserPrompt(ctx, d, meta)
}
func readUserPrompt(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectUserPromptProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectUserPrompt(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading User Prompt %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
userPrompt, resp, getErr, retryable := proxy.getArchitectUserPrompt(ctx, d.Id(), true, true, nil)
if retryable {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read User Prompt %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read User Prompt %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", userPrompt.Name)
resourcedata.SetNillableValue(d, "description", userPrompt.Description)
if resourcesSet, ok := d.Get("resources").(*schema.Set); ok && resourcesSet != nil {
promptResources := resourcesSet.List()
for _, promptResource := range promptResources {
resourceMap, ok := promptResource.(map[string]interface{})
if !ok {
continue
}
resourceFilename, ok := resourceMap["filename"].(string)
if !ok || resourceFilename == "" {
continue
}
APIResources := *userPrompt.Resources
for _, APIResource := range APIResources {
if APIResource.Tags == nil {
continue
}
tags := *APIResource.Tags
filenameTag, ok := tags["filename"]
if !ok {
continue
}
if len(filenameTag) > 0 {
if filenameTag[0] == resourceFilename {
if *APIResource.UploadStatus != "transcoded" {
return retry.RetryableError(fmt.Errorf("prompt file not transcoded. User prompt ID: '%s'. Filename: '%s'", d.Id(), resourceFilename))
}
}
}
}
}
}
_ = d.Set("resources", flattenPromptResources(d, userPrompt.Resources))
log.Printf("Read Audio Prompt %s %s", d.Id(), *userPrompt.Id)
return cc.CheckState(d)
})
}
func updateUserPrompt(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectUserPromptProxy(sdkConfig)
prompt := platformclientv2.Prompt{
Name: &name,
}
if description != "" {
prompt.Description = &description
}
log.Printf("Updating user prompt %s", name)
_, resp, err := proxy.updateArchitectUserPrompt(ctx, d.Id(), prompt)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update user prompt %s: %s", name, err), resp)
}
diagErr := updatePromptResource(ctx, d, proxy, sdkConfig)
if diagErr != nil {
return diagErr
}
log.Printf("Updated User Prompt %s", d.Id())
return readUserPrompt(ctx, d, meta)
}
func deleteUserPrompt(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectUserPromptProxy(sdkConfig)
log.Printf("Deleting user prompt %s", name)
if resp, err := proxy.deleteArchitectUserPrompt(ctx, d.Id(), true); err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete user prompt %s: %s", name, err), resp)
}
log.Printf("Deleted user prompt %s", name)
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err, retryable := proxy.getArchitectUserPrompt(ctx, d.Id(), false, false, nil)
if retryable {
if resp != nil && resp.StatusCode == 404 {
// User prompt deleted
log.Printf("Deleted user prompt %s", name)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting user prompt %s | error: %s", name, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("user prompt %s still exists", name), resp))
})
}
func updatePromptResource(ctx context.Context, d *schema.ResourceData, proxy *architectUserPromptProxy, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
name := d.Get("name").(string)
// Get the prompt so we can get existing prompt resources
userPrompt, resp, err, _ := proxy.getArchitectUserPrompt(ctx, d.Id(), true, true, nil)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get user prompt %s: %s", d.Id(), err), resp)
}
// Update the prompt resources
if resources, ok := d.GetOk("resources"); ok && resources != nil {
promptResources := resources.(*schema.Set).List()
for _, promptResource := range promptResources {
var userPromptResource *platformclientv2.Promptasset
languageExists := false
resourceMap := promptResource.(map[string]interface{})
resourceLanguage := resourceMap["language"].(string)
tag := make(map[string][]string)
tag["filename"] = []string{resourceMap["filename"].(string)}
// Check if language resource already exists
for _, v := range *userPrompt.Resources {
if *v.Language == resourceLanguage {
languageExists = true
break
}
}
if languageExists {
// Update existing resource
promptResource := platformclientv2.Promptasset{
Language: &resourceLanguage,
Tags: &tag,
}
resourceTtsString := resourceMap["tts_string"]
if resourceTtsString != nil || resourceTtsString.(string) != "" {
strResourceTtsString := resourceTtsString.(string)
promptResource.TtsString = &strResourceTtsString
}
resourceText := resourceMap["text"]
if resourceText != nil || resourceText.(string) != "" {
strResourceText := resourceText.(string)
promptResource.Text = &strResourceText
}
log.Printf("Updating user prompt resource for language: %s", resourceLanguage)
res, resp, err := proxy.updateArchitectUserPromptResource(ctx, *userPrompt.Id, resourceLanguage, promptResource)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create user prompt resource %s: %s", name, err), resp)
}
userPromptResource = res
} else {
// Create new resource for language
promptResource := platformclientv2.Promptassetcreate{
Language: &resourceLanguage,
Tags: &tag,
}
resourceTtsString := resourceMap["tts_string"]
if resourceTtsString != nil || resourceTtsString.(string) != "" {
strResourceTtsString := resourceTtsString.(string)
promptResource.TtsString = &strResourceTtsString
}
resourceText := resourceMap["text"]
if resourceText != nil || resourceText.(string) != "" {
strResourceText := resourceText.(string)
promptResource.Text = &strResourceText
}
log.Printf("Creating user prompt resource for language: %s", resourceLanguage)
res, resp, err := proxy.createArchitectUserPromptResource(ctx, *userPrompt.Id, promptResource)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create user prompt resource %s: %s", name, err), resp)
}
userPromptResource = res
}
uploadUri := userPromptResource.UploadUri
resourceFilename := resourceMap["filename"]
if resourceFilename == nil || resourceFilename.(string) == "" {
continue
}
resourceFilenameStr := resourceFilename.(string)
if err := uploadPrompt(uploadUri, &resourceFilenameStr, sdkConfig); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to upload user prompt resource %s", name), err)
}
log.Printf("Successfully uploaded user prompt resource for language: %s", resourceLanguage)
}
}
return nil
}
func getArchitectPromptAudioData(ctx context.Context, promptId string, meta interface{}) ([]PromptAudioData, error) {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getArchitectUserPromptProxy(sdkConfig)
data, _, err, _ := proxy.getArchitectUserPrompt(ctx, promptId, true, true, nil)
if err != nil {
return nil, err
}
var promptResourceData []PromptAudioData
for _, r := range *data.Resources {
var data PromptAudioData
if r.MediaUri != nil && *r.MediaUri != "" {
data.MediaUri = *r.MediaUri
data.Language = *r.Language
data.FileName = fmt.Sprintf("%s-%s.wav", *r.Language, promptId)
promptResourceData = append(promptResourceData, data)
}
}
return promptResourceData, nil
}
package architect_user_prompt
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"path"
"path/filepath"
"terraform-provider-genesyscloud/genesyscloud/util"
files "terraform-provider-genesyscloud/genesyscloud/util/files"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type PromptAudioData struct {
Language string
FileName string
MediaUri string
}
type UserPromptStruct struct {
ResourceID string
Name string
Description string
Resources []*UserPromptResourceStruct
}
type UserPromptResourceStruct struct {
Language string
Tts_string string
Text string
Filename string
FileContentHash string
}
func flattenPromptResources(d *schema.ResourceData, promptResources *[]platformclientv2.Promptasset) *schema.Set {
if promptResources == nil || len(*promptResources) == 0 {
return nil
}
resourceSet := schema.NewSet(schema.HashResource(userPromptResource), []interface{}{})
for _, sdkPromptAsset := range *promptResources {
promptResource := make(map[string]interface{})
if sdkPromptAsset.Language != nil {
promptResource["language"] = *sdkPromptAsset.Language
}
if sdkPromptAsset.TtsString != nil {
promptResource["tts_string"] = *sdkPromptAsset.TtsString
}
if sdkPromptAsset.Text != nil {
promptResource["text"] = *sdkPromptAsset.Text
}
if sdkPromptAsset.Tags != nil && len(*sdkPromptAsset.Tags) > 0 {
t := *sdkPromptAsset.Tags
promptResource["filename"] = t["filename"][0]
}
if schemaResources, ok := d.Get("resources").(*schema.Set); ok {
schemaResourcesList := schemaResources.List()
for _, r := range schemaResourcesList {
if rMap, ok := r.(map[string]interface{}); ok {
if fmt.Sprintf("%v", rMap["language"]) != *sdkPromptAsset.Language {
continue
}
if hash, ok := rMap["file_content_hash"].(string); ok && hash != "" {
promptResource["file_content_hash"] = hash
}
}
}
}
resourceSet.Add(promptResource)
}
return resourceSet
}
// Replace (or create) the filenames key in configMap with the FileName fields in audioDataList
// which point towards the downloaded audio files stored in the export folder.
// Since a language can only appear once in a resources array, we can match resources[n]["language"] with audioDataList[n].Language
func updateFilenamesInExportConfigMap(configMap map[string]interface{}, audioDataList []PromptAudioData, subDir string) {
if resources, ok := configMap["resources"].([]interface{}); ok && len(resources) > 0 {
for _, resource := range resources {
if r, ok := resource.(map[string]interface{}); ok {
fileName := ""
languageStr := r["language"].(string)
for _, data := range audioDataList {
if data.Language == languageStr {
fileName = data.FileName
break
}
}
if fileName != "" {
r["filename"] = path.Join(subDir, fileName)
r["file_content_hash"] = fmt.Sprintf(`${filesha256("%s")}`, path.Join(subDir, fileName))
}
}
}
}
}
func GenerateUserPromptResource(userPrompt *UserPromptStruct) string {
resourcesString := ``
for _, p := range userPrompt.Resources {
var fileContentHash string
if p.FileContentHash != util.NullValue {
fullyQualifiedPath, _ := filepath.Abs(p.FileContentHash)
fileContentHash = fmt.Sprintf(`filesha256("%s")`, fullyQualifiedPath)
} else {
fileContentHash = util.NullValue
}
resourcesString += fmt.Sprintf(`resources {
language = "%s"
tts_string = %s
text = %s
filename = %s
file_content_hash = %s
}
`,
p.Language,
p.Tts_string,
p.Text,
p.Filename,
fileContentHash,
)
}
return fmt.Sprintf(`resource "genesyscloud_architect_user_prompt" "%s" {
name = "%s"
description = %s
%s
}
`, userPrompt.ResourceID,
userPrompt.Name,
userPrompt.Description,
resourcesString,
)
}
func ArchitectPromptAudioResolver(promptId, exportDirectory, subDirectory string, configMap map[string]interface{}, meta interface{}) error {
fullPath := path.Join(exportDirectory, subDirectory)
if err := os.MkdirAll(fullPath, os.ModePerm); err != nil {
return err
}
audioDataList, err := getArchitectPromptAudioData(context.TODO(), promptId, meta)
if err != nil || len(audioDataList) == 0 {
return err
}
for _, data := range audioDataList {
if err := files.DownloadExportFile(fullPath, data.FileName, data.MediaUri); err != nil {
return err
}
}
updateFilenamesInExportConfigMap(configMap, audioDataList, subDirectory)
return nil
}
func uploadPrompt(uploadUri *string, filename *string, sdkConfig *platformclientv2.Configuration) error {
reader, file, err := files.DownloadOrOpenFile(*filename)
if file != nil {
defer file.Close()
}
if err != nil {
return err
}
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", filepath.Base(*filename))
if err != nil {
return err
}
if file != nil {
io.Copy(part, file)
} else {
io.Copy(part, reader)
}
io.Copy(part, file)
writer.Close()
request, err := http.NewRequest(http.MethodPost, *uploadUri, body)
if err != nil {
return err
}
request.Header.Add("Content-Type", writer.FormDataContentType())
request.Header.Add("Authorization", sdkConfig.AccessToken)
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
content, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
log.Printf("Content of upload: %s", content)
return nil
}
package auth_role
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
)
/*
The data_source_genesyscloud_auth_role.go contains the data source implementation
for the resource.
*/
// dataSourceAuthRoleRead retrieves by name the id in question
func DataSourceAuthRoleRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
name := d.Get("name").(string)
// Query role by name. Retry in case search has not yet indexed the role.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageSize = 100
const pageNum = 1
roles, proxyResponse, getErr := authAPI.GetAuthorizationRoles(pageSize, pageNum, "", nil, "", "", name, nil, nil, false, nil)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting role %s | error: %s", name, getErr), proxyResponse))
}
if roles.Entities == nil || len(*roles.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No authorization roles found with name %s", name), proxyResponse))
}
role := (*roles.Entities)[0]
d.SetId(*role.Id)
return nil
})
}
package auth_role
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_auth_role_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *authRoleProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createAuthRoleFunc func(ctx context.Context, p *authRoleProxy, domainOrganizationRole *platformclientv2.Domainorganizationrolecreate) (*platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error)
type getAllAuthRoleFunc func(ctx context.Context, p *authRoleProxy) (*[]platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error)
type getAuthRoleIdByNameFunc func(ctx context.Context, p *authRoleProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getAuthRoleByIdFunc func(ctx context.Context, p *authRoleProxy, id string) (domainOrganizationRole *platformclientv2.Domainorganizationrole, response *platformclientv2.APIResponse, err error)
type getDefaultRoleIdFunc func(ctx context.Context, p *authRoleProxy, defaultRoleID string) (roleId string, response *platformclientv2.APIResponse, err error)
type updateAuthRoleFunc func(ctx context.Context, p *authRoleProxy, id string, domainOrganizationRole *platformclientv2.Domainorganizationroleupdate) (*platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error)
type deleteAuthRoleFunc func(ctx context.Context, p *authRoleProxy, id string) (response *platformclientv2.APIResponse, err error)
type restoreDefaultRolesFunc func(ctx context.Context, p *authRoleProxy, roles *[]platformclientv2.Domainorganizationrole) (*platformclientv2.APIResponse, error)
type getAllowedPermissionsFunc func(p *authRoleProxy, domain string) (*map[string][]platformclientv2.Domainpermission, *platformclientv2.APIResponse, error)
// authRoleProxy contains all of the methods that call genesys cloud APIs.
type authRoleProxy struct {
clientConfig *platformclientv2.Configuration
authorizationApi *platformclientv2.AuthorizationApi
createAuthRoleAttr createAuthRoleFunc
getAllAuthRoleAttr getAllAuthRoleFunc
getAuthRoleIdByNameAttr getAuthRoleIdByNameFunc
getAuthRoleByIdAttr getAuthRoleByIdFunc
getDefaultRoleIdAttr getDefaultRoleIdFunc
updateAuthRoleAttr updateAuthRoleFunc
deleteAuthRoleAttr deleteAuthRoleFunc
restoreDefaultRolesAttr restoreDefaultRolesFunc
getAllowedPermissionsAttr getAllowedPermissionsFunc
}
// newAuthRoleProxy initializes the auth role proxy with all of the data needed to communicate with Genesys Cloud
func newAuthRoleProxy(clientConfig *platformclientv2.Configuration) *authRoleProxy {
api := platformclientv2.NewAuthorizationApiWithConfig(clientConfig)
return &authRoleProxy{
clientConfig: clientConfig,
authorizationApi: api,
createAuthRoleAttr: createAuthRoleFn,
getAllAuthRoleAttr: getAllAuthRoleFn,
getAuthRoleIdByNameAttr: getAuthRoleIdByNameFn,
getAuthRoleByIdAttr: getAuthRoleByIdFn,
getDefaultRoleIdAttr: getDefaultRoleIdFn,
updateAuthRoleAttr: updateAuthRoleFn,
deleteAuthRoleAttr: deleteAuthRoleFn,
restoreDefaultRolesAttr: restoreDefaultRolesFn,
getAllowedPermissionsAttr: getAllowedPermissionsFn,
}
}
// getAuthRoleProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getAuthRoleProxy(clientConfig *platformclientv2.Configuration) *authRoleProxy {
if internalProxy == nil {
internalProxy = newAuthRoleProxy(clientConfig)
}
return internalProxy
}
// createAuthRole creates a Genesys Cloud auth role
func (p *authRoleProxy) createAuthRole(ctx context.Context, authRole *platformclientv2.Domainorganizationrolecreate) (*platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error) {
return p.createAuthRoleAttr(ctx, p, authRole)
}
// getAuthRole retrieves all Genesys Cloud auth role
func (p *authRoleProxy) getAllAuthRole(ctx context.Context) (*[]platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error) {
return p.getAllAuthRoleAttr(ctx, p)
}
// getAuthRoleIdByName returns a single Genesys Cloud auth role by a name
func (p *authRoleProxy) getAuthRoleIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getAuthRoleIdByNameAttr(ctx, p, name)
}
// getAuthRoleById returns a single Genesys Cloud auth role by Id
func (p *authRoleProxy) getAuthRoleById(ctx context.Context, id string) (authRole *platformclientv2.Domainorganizationrole, response *platformclientv2.APIResponse, err error) {
return p.getAuthRoleByIdAttr(ctx, p, id)
}
// getAuthRoleById returns a single Genesys Cloud auth role by Id
func (p *authRoleProxy) getDefaultRoleById(ctx context.Context, defaultRoleId string) (roleId string, response *platformclientv2.APIResponse, err error) {
return p.getDefaultRoleIdAttr(ctx, p, defaultRoleId)
}
// updateAuthRole updates a Genesys Cloud auth role
func (p *authRoleProxy) updateAuthRole(ctx context.Context, id string, authRole *platformclientv2.Domainorganizationroleupdate) (*platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error) {
return p.updateAuthRoleAttr(ctx, p, id, authRole)
}
// deleteAuthRole deletes a Genesys Cloud auth role by Id
func (p *authRoleProxy) deleteAuthRole(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteAuthRoleAttr(ctx, p, id)
}
func (p *authRoleProxy) restoreDefaultRoles(ctx context.Context, roles *[]platformclientv2.Domainorganizationrole) (*platformclientv2.APIResponse, error) {
return p.restoreDefaultRolesAttr(ctx, p, roles)
}
// getAllowedPermissions returns an array of available permissions for a given domain e.g. outbound
func (p *authRoleProxy) getAllowedPermissions(domain string) (*map[string][]platformclientv2.Domainpermission, *platformclientv2.APIResponse, error) {
return p.getAllowedPermissionsAttr(p, domain)
}
// createAuthRoleFn is an implementation function for creating a Genesys Cloud auth role
func createAuthRoleFn(ctx context.Context, p *authRoleProxy, authRole *platformclientv2.Domainorganizationrolecreate) (*platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error) {
role, apiResponse, err := p.authorizationApi.PostAuthorizationRoles(*authRole)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to create role %s: %s", *authRole.Name, err)
}
return role, apiResponse, nil
}
// getAllAuthRoleFn is the implementation for retrieving all auth role in Genesys Cloud
func getAllAuthRoleFn(ctx context.Context, p *authRoleProxy) (*[]platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error) {
const pageSize = 100
var allAuthRoles []platformclientv2.Domainorganizationrole
roles, resp, getErr := p.authorizationApi.GetAuthorizationRoles(pageSize, 1, "", nil, "", "", "", nil, nil, false, nil)
if getErr != nil {
return nil, resp, fmt.Errorf("failed to get page of auth roles: %s", getErr)
}
if roles.Entities != nil && len(*roles.Entities) > 0 {
allAuthRoles = append(allAuthRoles, *roles.Entities...)
}
for pageNum := 2; pageNum <= *roles.PageCount; pageNum++ {
roles, resp, getErr := p.authorizationApi.GetAuthorizationRoles(pageSize, pageNum, "", nil, "", "", "", nil, nil, false, nil)
if getErr != nil {
return nil, resp, fmt.Errorf("failed to get page of auth roles: %s", getErr)
}
if roles.Entities == nil || len(*roles.Entities) == 0 {
break
}
allAuthRoles = append(allAuthRoles, *roles.Entities...)
}
return &allAuthRoles, resp, nil
}
// getAuthRoleIdByNameFn is an implementation of the function to get a Genesys Cloud auth role by name
func getAuthRoleIdByNameFn(ctx context.Context, p *authRoleProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return "", false, nil, nil
}
// getAuthRoleByIdFn is an implementation of the function to get a Genesys Cloud auth role by Id
func getAuthRoleByIdFn(ctx context.Context, p *authRoleProxy, id string) (authRole *platformclientv2.Domainorganizationrole, response *platformclientv2.APIResponse, err error) {
role, apiResponse, err := p.authorizationApi.GetAuthorizationRole(id, false, nil)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to retrieve role %s by id: %s", id, err)
}
return role, apiResponse, nil
}
func getDefaultRoleIdFn(ctx context.Context, p *authRoleProxy, defaultRoleID string) (roleId string, response *platformclientv2.APIResponse, err error) {
const pageSize = 1
const pageNum = 1
roles, apiResponse, getErr := p.authorizationApi.GetAuthorizationRoles(pageSize, pageNum, "", nil, "", "", "", nil, []string{defaultRoleID}, false, nil)
if getErr != nil {
return "", apiResponse, fmt.Errorf("Error requesting default role %s: %s", defaultRoleID, getErr)
}
if roles.Entities == nil || len(*roles.Entities) == 0 {
return "", apiResponse, fmt.Errorf("Default role not found: %s", defaultRoleID)
}
return *(*roles.Entities)[0].Id, apiResponse, nil
}
// updateAuthRoleFn is an implementation of the function to update a Genesys Cloud auth role
func updateAuthRoleFn(ctx context.Context, p *authRoleProxy, id string, authRole *platformclientv2.Domainorganizationroleupdate) (*platformclientv2.Domainorganizationrole, *platformclientv2.APIResponse, error) {
role, apiResponse, err := p.authorizationApi.PutAuthorizationRole(id, *authRole)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to update role %s: %s", id, err)
}
return role, apiResponse, nil
}
// deleteAuthRoleFn is an implementation function for deleting a Genesys Cloud auth role
func deleteAuthRoleFn(ctx context.Context, p *authRoleProxy, id string) (response *platformclientv2.APIResponse, err error) {
apiResponse, err := p.authorizationApi.DeleteAuthorizationRole(id)
if err != nil {
return apiResponse, err
}
return apiResponse, nil
}
func restoreDefaultRolesFn(ctx context.Context, p *authRoleProxy, roles *[]platformclientv2.Domainorganizationrole) (*platformclientv2.APIResponse, error) {
_, apiResponse, err := p.authorizationApi.PutAuthorizationRolesDefault(*roles)
if err != nil {
return apiResponse, err
}
return apiResponse, nil
}
// getAllowedPermissionsFn is an implementation function for getting all allowed permissions for a domain
func getAllowedPermissionsFn(p *authRoleProxy, domain string) (*map[string][]platformclientv2.Domainpermission, *platformclientv2.APIResponse, error) {
const pageSize = 100
allowedPermissions := make(map[string][]platformclientv2.Domainpermission)
permissions, apiResponse, err := p.authorizationApi.GetAuthorizationPermissions(pageSize, 1, "domain", domain)
if err != nil {
return nil, apiResponse, err
}
if permissions.Entities == nil || len(*permissions.Entities) == 0 {
return &allowedPermissions, apiResponse, nil
}
for _, permission := range *permissions.Entities {
for entityType, entityPermissions := range *permission.PermissionMap {
allowedPermissions[entityType] = entityPermissions
}
}
for pageNum := 2; pageNum <= *permissions.PageCount; pageNum++ {
permissions, apiResponse, err := p.authorizationApi.GetAuthorizationPermissions(pageSize, pageNum, "domain", domain)
if err != nil {
return nil, apiResponse, err
}
if permissions.Entities == nil || len(*permissions.Entities) == 0 {
break
}
for _, permission := range *permissions.Entities {
for entityType, entityPermissions := range *permission.PermissionMap {
allowedPermissions[entityType] = entityPermissions
}
}
}
return &allowedPermissions, apiResponse, nil
}
package auth_role
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
)
/*
The resource_genesyscloud_auth_role_utils.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthAuthRole retrieves all of the auth role via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthAuthRoles(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getAuthRoleProxy(clientConfig)
roles, proxyResponse, getErr := proxy.getAllAuthRole(ctx)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of roles %s", getErr), proxyResponse)
}
for _, role := range *roles {
resources[*role.Id] = &resourceExporter.ResourceMeta{Name: *role.Name}
}
return resources, nil
}
// createAuthRole is used by the auth_role resource to create Genesys cloud auth role
func createAuthRole(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getAuthRoleProxy(sdkConfig)
// Validate each permission policy exists before continuing
// This is a workaround for a bug in the auth roles APIs
// Bug reported to auth team in ticket AUTHZ-315
policies := buildSdkRolePermPolicies(d)
if policies != nil {
for _, policy := range *policies {
resp, err := validatePermissionPolicy(proxy, policy)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Permission policy not found: %s, ensure your org has the required product for this permission", err), resp)
}
}
}
name := d.Get("name").(string)
description := d.Get("description").(string)
defaultRoleID := d.Get("default_role_id").(string)
log.Printf("Creating role %s", name)
if defaultRoleID != "" {
// Default roles must already exist, or they cannot be modified
defaultRole, proxyResponse, err := proxy.getDefaultRoleById(ctx, defaultRoleID)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get default role %s error: %s", d.Id(), err), proxyResponse)
}
d.SetId(defaultRole)
return updateAuthRole(ctx, d, meta)
}
roleObj := platformclientv2.Domainorganizationrolecreate{
Name: &name,
Description: &description,
Permissions: buildSdkRolePermissions(d),
PermissionPolicies: policies,
}
role, proxyResponse, err := proxy.createAuthRole(ctx, &roleObj)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create role %s %s errror: %s", d.Id(), *roleObj.Name, err), proxyResponse)
}
d.SetId(*role.Id)
log.Printf("Created role %s %s", name, *role.Id)
return readAuthRole(ctx, d, meta)
}
// readAuthRole is used by the auth_role resource to read an auth role from genesys cloud
func readAuthRole(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getAuthRoleProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceAuthRole(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading role %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
role, proxyResponse, getErr := proxy.getAuthRoleById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(proxyResponse) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read role %s | error: %s", d.Id(), getErr), proxyResponse))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read role %s | error: %s", d.Id(), getErr), proxyResponse))
}
d.Set("name", *role.Name)
resourcedata.SetNillableValue(d, "description", role.Description)
resourcedata.SetNillableValue(d, "default_role_id", role.DefaultRoleId)
if role.Permissions != nil {
d.Set("permissions", lists.StringListToSet(*role.Permissions))
} else {
d.Set("permissions", nil)
}
if role.PermissionPolicies != nil {
d.Set("permission_policies", flattenRolePermissionPolicies(*role.PermissionPolicies))
} else {
d.Set("permission_policies", nil)
}
log.Printf("Read role %s %s", d.Id(), *role.Name)
return cc.CheckState(d)
})
}
// updateAuthRole is used by the auth_role resource to update an auth role in Genesys Cloud
func updateAuthRole(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getAuthRoleProxy(sdkConfig)
// Validate each permission policy exists before continuing
// This is a workaround for a bug in the auth roles APIs
// Bug reported to auth team in ticket AUTHZ-315
policies := buildSdkRolePermPolicies(d)
if policies != nil {
for _, policy := range *policies {
resp, err := validatePermissionPolicy(proxy, policy)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Permission policy not found: %s, ensure your org has the required product for this permission", err), resp)
}
}
}
name := d.Get("name").(string)
description := d.Get("description").(string)
defaultRoleID := d.Get("default_role_id").(string)
log.Printf("Updating role %s", name)
roleObj := platformclientv2.Domainorganizationroleupdate{
Name: &name,
Description: &description,
Permissions: buildSdkRolePermissions(d),
PermissionPolicies: policies,
DefaultRoleId: &defaultRoleID,
}
_, proxyResponse, err := proxy.updateAuthRole(ctx, d.Id(), &roleObj)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update role %s %s error: %s", d.Id(), *roleObj.Name, err), proxyResponse)
}
log.Printf("Updated role %s", name)
return readAuthRole(ctx, d, meta)
}
// deleteAuthRole is used by the auth_role resource to delete an auth role from Genesys cloud
func deleteAuthRole(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getAuthRoleProxy(sdkConfig)
name := d.Get("name").(string)
defaultRoleID := d.Get("default_role_id").(string)
if defaultRoleID != "" {
// Restore default roles to their default state instead of deleting them
log.Printf("Restoring default role %s", name)
id := d.Id()
proxyResponse, err := proxy.restoreDefaultRoles(ctx, &[]platformclientv2.Domainorganizationrole{
{
Id: &id,
},
})
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to restore default role %s error: %s", defaultRoleID, err), proxyResponse)
}
return nil
}
log.Printf("Deleting role %s", name)
proxyResponse, err := proxy.deleteAuthRole(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete auth role %s %s error: %s", d.Id(), name, err), proxyResponse)
}
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
_, proxyResponse, err := proxy.getAuthRoleById(ctx, d.Id())
if err != nil {
if util.IsStatus404(proxyResponse) {
// role deleted
log.Printf("Deleted role %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting role %s | error: %s", d.Id(), err), proxyResponse))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Role %s still exists", d.Id()), proxyResponse))
})
}
package auth_role
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_auth_role_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the auth_role resource.
3. The datasource schema definitions for the auth_role datasource.
4. The resource exporter configuration for the auth_role exporter.
*/
const resourceName = "genesyscloud_auth_role"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceAuthRole())
regInstance.RegisterDataSource(resourceName, DataSourceAuthRole())
regInstance.RegisterExporter(resourceName, AuthRoleExporter())
}
var (
rolePermPolicyCondOperands = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "Value type (USER | QUEUE | SCALAR | VARIABLE).",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"USER", "QUEUE", "SCALAR", "VARIABLE"}, false),
},
"queue_id": {
Description: "Queue ID for QUEUE types.",
Type: schema.TypeString,
Optional: true,
},
"user_id": {
Description: "User ID for USER types.",
Type: schema.TypeString,
Optional: true,
},
"value": {
Description: "Value for operand. For USER or QUEUE types, use user_id or queue_id instead.",
Type: schema.TypeString,
Optional: true,
},
},
}
rolePermPolicyCondTerms = &schema.Resource{
Schema: map[string]*schema.Schema{
"variable_name": {
Description: "Variable name being compared. This varies depending on the permission.",
Type: schema.TypeString,
Required: true,
},
"operator": {
Description: "Operator type (EQ | IN | GE | GT | LE | LT).",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"EQ", "IN", "GE", "GT", "LE", "LT"}, false),
},
"operands": {
Description: "Operands for this condition.",
Type: schema.TypeSet,
Required: true,
Elem: rolePermPolicyCondOperands,
},
},
}
rolePermPolicyResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"domain": {
Description: "Permission domain. e.g 'directory'",
Type: schema.TypeString,
Required: true,
},
"entity_name": {
Description: "Permission entity or '*' for all. e.g. 'user'",
Type: schema.TypeString,
Required: true,
},
"action_set": {
Description: "Actions allowed on the entity or '*' for all. e.g. 'add'",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
MinItems: 1,
},
"conditions": {
Description: "Conditions specific to this resource. This is only applicable to some permission types.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"conjunction": {
Description: "Conjunction for condition terms (AND | OR).",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"AND", "OR"}, false),
},
"terms": {
Description: "Terms of the condition.",
Type: schema.TypeSet,
Required: true,
Elem: rolePermPolicyCondTerms,
},
},
},
},
},
}
)
// ResourceAuthRole registers the genesyscloud_auth_role resource with Terraform
func ResourceAuthRole() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Authorization Role",
CreateContext: provider.CreateWithPooledClient(createAuthRole),
ReadContext: provider.ReadWithPooledClient(readAuthRole),
UpdateContext: provider.UpdateWithPooledClient(updateAuthRole),
DeleteContext: provider.DeleteWithPooledClient(deleteAuthRole),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Role name. This cannot be modified for default roles.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "Role description.",
Type: schema.TypeString,
Optional: true,
},
"permissions": {
Description: "General role permissions. e.g. 'group_creation'",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"permission_policies": {
Description: "Role permission policies.",
Type: schema.TypeSet,
Optional: true,
Elem: rolePermPolicyResource,
},
"default_role_id": {
Description: "Internal ID for an existing default role, e.g. 'employee'. This can be set to manage permissions on existing default roles. Note: Changing the default_role_id attribute will cause this auth_role to be dropped and recreated with a new ID.",
Type: schema.TypeString,
ForceNew: true,
Optional: true,
},
},
}
}
// AuthRoleExporter returns the resourceExporter object used to hold the genesyscloud_auth_role exporter's config
func AuthRoleExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthAuthRoles),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"permission_policies.conditions.terms.operands.queue_id": {RefType: "genesyscloud_routing_queue"},
"permission_policies.conditions.terms.operands.user_id": {RefType: "genesyscloud_user"},
},
RemoveIfMissing: map[string][]string{
"permission_policies.conditions.terms.operands": {"queue_id", "user_id", "value"},
"permission_policies.conditions.terms": {"operands"},
"permission_policies.conditions": {"terms"},
},
}
}
// DataSourceAuthRole registers the genesyscloud_auth_role data source
func DataSourceAuthRole() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Roles. Select a role by name.`,
ReadContext: provider.ReadWithPooledClient(DataSourceAuthRoleRead),
Schema: map[string]*schema.Schema{
"name": {
Description: `Role name.`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package auth_role
import (
"fmt"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func validatePermissionPolicy(proxy *authRoleProxy, policy platformclientv2.Domainpermissionpolicy) (*platformclientv2.APIResponse, error) {
allowedPermissions, resp, err := proxy.getAllowedPermissions(*policy.Domain)
if err != nil {
return resp, fmt.Errorf("error requesting org permissions: %s", err)
}
if len(*allowedPermissions) == 0 {
return resp, fmt.Errorf("domain %s not found", *policy.Domain)
}
if *policy.EntityName == "*" {
return resp, nil
}
// Check entity type (e.g. callableTimeSet) exists in the map of allowed permissions
if entityPermissions, ok := (*allowedPermissions)[*policy.EntityName]; ok {
// Check if the policy actions exist for the given domain permission e.g. callableTimeSet: add
for _, action := range *policy.ActionSet {
if action == "*" && len(entityPermissions) >= 1 {
break
}
var found bool
for _, entityPermission := range entityPermissions {
if action == *entityPermission.Action {
// action found, move to next action
found = true
break
}
}
if !found {
return resp, fmt.Errorf("action %s not found for domain %s, entity name %s", action, *policy.Domain, *policy.EntityName)
}
}
// All actions have been found, permission exists
return resp, nil
}
return resp, fmt.Errorf("entity_name %s not found for domain %s", *policy.EntityName, *policy.Domain)
}
func buildSdkRolePermissions(d *schema.ResourceData) *[]string {
if permConfig, ok := d.GetOk("permissions"); ok {
return lists.SetToStringList(permConfig.(*schema.Set))
}
return nil
}
func buildSdkRolePermPolicies(d *schema.ResourceData) *[]platformclientv2.Domainpermissionpolicy {
var sdkPolicies []platformclientv2.Domainpermissionpolicy
if configPolicies, ok := d.GetOk("permission_policies"); ok {
policyList := configPolicies.(*schema.Set).List()
for _, configPolicy := range policyList {
policyMap := configPolicy.(map[string]interface{})
domain := policyMap["domain"].(string)
entityName := policyMap["entity_name"].(string)
policy := platformclientv2.Domainpermissionpolicy{
Domain: &domain,
EntityName: &entityName,
ActionSet: buildSdkPermPolicyActions(policyMap),
}
if conditions, ok := policyMap["conditions"]; ok {
conditionsList := conditions.([]interface{})
policy.ResourceConditionNode = buildSdkPermPolicyConditions(conditionsList)
}
sdkPolicies = append(sdkPolicies, policy)
}
}
return &sdkPolicies
}
func buildSdkPermPolicyActions(policyAttrs map[string]interface{}) *[]string {
if actions, ok := policyAttrs["action_set"]; ok {
return lists.SetToStringList(actions.(*schema.Set))
}
return nil
}
func buildSdkPermPolicyConditions(conditions []interface{}) *platformclientv2.Domainresourceconditionnode {
if len(conditions) > 0 {
conditionAttrs := conditions[0].(map[string]interface{})
conjunction := conditionAttrs["conjunction"].(string)
terms := conditionAttrs["terms"].(*schema.Set).List()
return &platformclientv2.Domainresourceconditionnode{
Conjunction: &conjunction,
Terms: buildSdkPermPolicyCondTerms(terms),
}
}
return nil
}
func buildSdkPermPolicyCondTerms(terms []interface{}) *[]platformclientv2.Domainresourceconditionnode {
sdkTerms := make([]platformclientv2.Domainresourceconditionnode, len(terms))
for i, term := range terms {
termMap := term.(map[string]interface{})
varName := termMap["variable_name"].(string)
operator := termMap["operator"].(string)
operands := termMap["operands"].(*schema.Set).List()
sdkTerms[i] = platformclientv2.Domainresourceconditionnode{
VariableName: &varName,
Operator: &operator,
Operands: buildSdkPermPolicyCondOperands(operands),
}
}
return &sdkTerms
}
func buildSdkPermPolicyCondOperands(operands []interface{}) *[]platformclientv2.Domainresourceconditionvalue {
sdkOperands := make([]platformclientv2.Domainresourceconditionvalue, len(operands))
for i, operand := range operands {
operandMap := operand.(map[string]interface{})
varType := operandMap["type"].(string)
sdkOperand := platformclientv2.Domainresourceconditionvalue{
VarType: &varType,
}
switch varType {
case "USER":
value := operandMap["user_id"].(string)
sdkOperand.User = &platformclientv2.User{Id: &value}
case "QUEUE":
value := operandMap["queue_id"].(string)
sdkOperand.Queue = &platformclientv2.Queue{Id: &value}
default:
value := operandMap["value"].(string)
sdkOperand.Value = &value
}
sdkOperands[i] = sdkOperand
}
return &sdkOperands
}
func flattenRolePermissionPolicies(policies []platformclientv2.Domainpermissionpolicy) *schema.Set {
policySet := schema.NewSet(schema.HashResource(rolePermPolicyResource), []interface{}{})
for _, sdkPolicy := range policies {
policyMap := make(map[string]interface{})
if sdkPolicy.Domain != nil {
policyMap["domain"] = *sdkPolicy.Domain
}
if sdkPolicy.EntityName != nil {
policyMap["entity_name"] = *sdkPolicy.EntityName
}
if sdkPolicy.ActionSet != nil {
policyMap["action_set"] = lists.StringListToSet(*sdkPolicy.ActionSet)
}
if sdkPolicy.ResourceConditionNode != nil {
policyMap["conditions"] = flattenRoleConditionNode(*sdkPolicy.ResourceConditionNode)
}
policySet.Add(policyMap)
}
return policySet
}
func flattenRoleConditionNode(conditions platformclientv2.Domainresourceconditionnode) []interface{} {
conditionMap := make(map[string]interface{})
if conditions.Conjunction != nil {
conditionMap["conjunction"] = *conditions.Conjunction
}
if conditions.Terms != nil {
conditionMap["terms"] = flattenRoleConditionTerms(*conditions.Terms)
}
return []interface{}{conditionMap}
}
func flattenRoleConditionTerms(terms []platformclientv2.Domainresourceconditionnode) *schema.Set {
termSet := schema.NewSet(schema.HashResource(rolePermPolicyCondTerms), []interface{}{})
for _, term := range terms {
termMap := make(map[string]interface{})
if term.VariableName != nil {
termMap["variable_name"] = *term.VariableName
}
if term.Operator != nil {
termMap["operator"] = *term.Operator
}
if term.Operands != nil {
termMap["operands"] = flattenRoleConditionOperands(*term.Operands)
}
termSet.Add(termMap)
}
return termSet
}
func flattenRoleConditionOperands(operands []platformclientv2.Domainresourceconditionvalue) *schema.Set {
operandSet := schema.NewSet(schema.HashResource(rolePermPolicyCondOperands), []interface{}{})
for _, operand := range operands {
operandMap := make(map[string]interface{})
if operand.VarType != nil {
operandMap["type"] = *operand.VarType
switch *operand.VarType {
case "USER":
if operand.User != nil {
operandMap["user_id"] = *operand.User.Id
}
case "QUEUE":
if operand.Queue != nil {
operandMap["queue_id"] = *operand.Queue.Id
}
default:
if operand.Value != nil {
operandMap["value"] = *operand.Value
}
}
}
operandSet.Add(operandMap)
}
return operandSet
}
func GenerateAuthRoleResource(
resourceID string,
name string,
description string,
nestedBlocks ...string) string {
return fmt.Sprintf(`resource "genesyscloud_auth_role" "%s" {
name = "%s"
description = "%s"
%s
}
`, resourceID, name, description, strings.Join(nestedBlocks, "\n"))
}
func GenerateRolePermissions(permissions ...string) string {
return fmt.Sprintf(`
permissions = [%s]
`, strings.Join(permissions, ","))
}
func GenerateRolePermPolicy(domain string, entityName string, actions ...string) string {
return fmt.Sprintf(` permission_policies {
domain = "%s"
entity_name = "%s"
action_set = [%s]
}
`, domain, entityName, strings.Join(actions, ","))
}
func GenerateDefaultAuthRoleDataSource(
resourceID string,
name string) string {
return fmt.Sprintf(`data "genesyscloud_auth_role" "%s" {
name = %s
}
`, resourceID, name)
}
package authorization_product
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
)
import (
"context"
"fmt"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
func dataSourceAuthorizationProductRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getauthProductProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
// Get the list of enabled products
authProductId, retryable, resp, err := proxy.getAuthorizationProduct(ctx, name)
if err != nil {
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get Authorization product %s | error: %s", authProductId, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get Authorization product %s | error: %s", authProductId, err), resp))
}
d.SetId(authProductId)
return nil
})
}
func GenerateAuthorizationProductDataSource(id, productName, dependsOn string) string {
return fmt.Sprintf(`
data "genesyscloud_authorization_product" "%s" {
name = "%s"
depends_on=[%s]
}
`, id, productName, dependsOn)
}
package authorization_product
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_authorization_product_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *authProductProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAuthorizationProductFunc func(ctx context.Context, p *authProductProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
// authProductProxy contains all of the methods that call genesys cloud APIs.
type authProductProxy struct {
clientConfig *platformclientv2.Configuration
authApi *platformclientv2.AuthorizationApi
getAuthorizationProductAttr getAuthorizationProductFunc
}
// newauthProductProxy initializes the authorization product proxy with all of the data needed to communicate with Genesys Cloud
func newauthProductProxy(clientConfig *platformclientv2.Configuration) *authProductProxy {
api := platformclientv2.NewAuthorizationApiWithConfig(clientConfig)
return &authProductProxy{
clientConfig: clientConfig,
authApi: api,
getAuthorizationProductAttr: getAuthorizationProductFn,
}
}
// getauthProductProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getauthProductProxy(clientConfig *platformclientv2.Configuration) *authProductProxy {
if internalProxy == nil {
internalProxy = newauthProductProxy(clientConfig)
}
return internalProxy
}
// getAuthorizationProduct returns a single Genesys Cloud authorization product by a name
func (p *authProductProxy) getAuthorizationProduct(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getAuthorizationProductAttr(ctx, p, name)
}
// getAuthorizationProductFn is an implementation of the function to get a Genesys Cloud authorization product by name
func getAuthorizationProductFn(ctx context.Context, p *authProductProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
authProducts, apiResponse, err := p.authApi.GetAuthorizationProducts()
if err != nil {
return "", true, apiResponse, fmt.Errorf("error requesting Auth Product %s: %s", name, err)
}
if authProducts.Entities == nil || len(*authProducts.Entities) == 0 {
return "", false, apiResponse, fmt.Errorf("No Auth Products found with name %s", name)
}
for _, entity := range *authProducts.Entities {
if *entity.Id == name {
return *entity.Id, false, apiResponse, nil
}
}
return "", false, apiResponse, fmt.Errorf("no Auth Product found with name %s", name)
}
package authorization_product
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
genesyscloud_authorization_product_schema holds four functions within it:
1. The registration code that registers the Datasource for the package.
2. The datasource schema definitions for the authorization_product datasource.
*/
const resourceName = "genesyscloud_authorization_product"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterDataSource(resourceName, DataSourceAuthorizationProduct())
}
// DataSourceAuthorizationProduct registers the authorization_product data source
func DataSourceAuthorizationProduct() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Authorisation Products.`,
ReadContext: provider.ReadWithPooledClient(dataSourceAuthorizationProductRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: `Authorization Product name.`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceSchedule() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Schedule. Select a schedule by name",
ReadContext: provider.ReadWithPooledClient(dataSourceScheduleRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Schedule name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceScheduleRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
archAPI := platformclientv2.NewArchitectApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageSize = 100
for pageNum := 1; ; pageNum++ {
schedule, resp, getErr := archAPI.GetArchitectSchedules(pageNum, pageSize, "", "", name, nil)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Error requesting schedule %s | error: %s", name, getErr), resp))
}
if schedule.Entities == nil || len(*schedule.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("No schedule found with name %s", name), resp))
}
d.SetId(*(*schedule.Entities)[0].Id)
return nil
}
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceAuthDivision() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Divisions. Select a division by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceAuthDivisionRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Division name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceAuthDivisionRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
name := d.Get("name").(string)
// Query division by name. Retry in case search has not yet indexed the division.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageSize = 100
const pageNum = 1
divisions, resp, getErr := authAPI.GetAuthorizationDivisions(pageSize, pageNum, "", nil, "", "", false, nil, name)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Error requesting division %s | error: %s", name, getErr), resp))
}
if divisions.Entities == nil || len(*divisions.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("No authorization divisions found with name %s", name), resp))
}
for _, division := range *divisions.Entities {
if *division.Name == name {
d.SetId(*division.Id)
return nil
}
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("No division with name %s found", name), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceAuthDivisionHome() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Divisions. Get the Home division",
ReadContext: provider.ReadWithPooledClient(dataSourceAuthDivisionHomeRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Home division name.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Description: "Home division description.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
func GenerateAuthDivisionHomeDataSource(resName string) string {
return fmt.Sprintf(`
data "genesyscloud_auth_division_home" "%s" {}
`, resName)
}
func dataSourceAuthDivisionHomeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
// Query home division
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
division, resp, getErr := authAPI.GetAuthorizationDivisionsHome()
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division_home", fmt.Sprintf("Error requesting divisions: %s", getErr), resp))
}
d.SetId(*division.Id)
d.Set("name", *division.Name)
if division.Description != nil {
d.Set("description", *division.Description)
}
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceJourneyActionMap() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Action Map. Select a journey action map by name",
ReadContext: provider.ReadWithPooledClient(dataSourceJourneyActionMapRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Journey Action Map name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceJourneyActionMapRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
var response *platformclientv2.APIResponse
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
journeyActionMaps, resp, getErr := journeyApi.GetJourneyActionmaps(pageNum, pageSize, "", "", "", nil, nil, "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("failed to get page of journey action maps: %v", getErr), resp))
}
response = resp
if journeyActionMaps.Entities == nil || len(*journeyActionMaps.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("no journey action map found with name %s", name), resp))
}
for _, actionMap := range *journeyActionMaps.Entities {
if actionMap.DisplayName != nil && *actionMap.DisplayName == name {
d.SetId(*actionMap.Id)
return nil
}
}
pageCount = *journeyActionMaps.PageCount
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("no journey action map found with name %s", name), response))
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceJourneyActionTemplate() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Action Template. Select a journey action template by name",
ReadContext: provider.ReadWithPooledClient(dataSourceJourneyActionTemplateRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Journey Action Template name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceJourneyActionTemplateRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
var response *platformclientv2.APIResponse
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
journeyActionTemplates, resp, getErr := journeyApi.GetJourneyActiontemplates(pageNum, pageSize, "", "", "", nil, "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("failed to get page of journey action template: %v", getErr), resp))
}
response = resp
if journeyActionTemplates.Entities == nil || len(*journeyActionTemplates.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("no journey action template found with name %s", name), resp))
}
for _, actionTemplate := range *journeyActionTemplates.Entities {
if actionTemplate.Name != nil && *actionTemplate.Name == name {
d.SetId(*actionTemplate.Id)
return nil
}
}
pageCount = *journeyActionTemplates.PageCount
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("no journey action template found with name %s", name), response))
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceJourneyOutcome() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Journey Outcome. Select a journey outcome by name",
ReadContext: provider.ReadWithPooledClient(dataSourceJourneyOutcomeRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Journey Outcome name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceJourneyOutcomeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
var response *platformclientv2.APIResponse
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
journeyOutcomes, resp, getErr := journeyApi.GetJourneyOutcomes(pageNum, pageSize, "", nil, nil, "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("failed to get page of journey outcomes: %v", getErr), resp))
}
response = resp
if journeyOutcomes.Entities == nil || len(*journeyOutcomes.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("no journey outcome found with name %s", name), resp))
}
for _, journeyOutcome := range *journeyOutcomes.Entities {
if journeyOutcome.DisplayName != nil && *journeyOutcome.DisplayName == name {
d.SetId(*journeyOutcome.Id)
return nil
}
}
pageCount = *journeyOutcomes.PageCount
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("no journey outcome found with name %s", name), response))
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceJourneySegment() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Journey Segment. Select a journey segment by name",
ReadContext: provider.ReadWithPooledClient(dataSourceJourneySegmentRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Journey Segment name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceJourneySegmentRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
var response *platformclientv2.APIResponse
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
journeySegments, resp, getErr := journeyApi.GetJourneySegments("", pageSize, pageNum, true, nil, nil, "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("failed to get page of journey segments: %v", getErr), resp))
}
response = resp
if journeySegments.Entities == nil || len(*journeySegments.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("no journey segment found with name %s", name), resp))
}
for _, journeySegment := range *journeySegments.Entities {
if journeySegment.DisplayName != nil && *journeySegment.DisplayName == name {
d.SetId(*journeySegment.Id)
return nil
}
}
pageCount = *journeySegments.PageCount
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("no journey segment found with name %s", name), response))
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceKnowledgeCategory() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Knowledge Base Category. Select a category by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceKnowledgeCategoryRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Knowledge base category name",
Type: schema.TypeString,
Required: true,
},
"knowledge_base_name": {
Description: "Knowledge base name",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceKnowledgeCategoryRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
name := d.Get("name").(string)
knowledgeBaseName := d.Get("knowledge_base_name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageSize = 100
publishedKnowledgeBases, publishedResp, getPublishedErr := knowledgeAPI.GetKnowledgeKnowledgebases("", "", "", fmt.Sprintf("%v", pageSize), knowledgeBaseName, "", true, "", "")
unpublishedKnowledgeBases, unpublishedResp, getUnpublishedErr := knowledgeAPI.GetKnowledgeKnowledgebases("", "", "", fmt.Sprintf("%v", pageSize), knowledgeBaseName, "", false, "", "")
if getPublishedErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to get knowledge base %s | error: %s", knowledgeBaseName, getPublishedErr), publishedResp))
}
if getUnpublishedErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to get knowledge base %s | error: %s", knowledgeBaseName, getUnpublishedErr), unpublishedResp))
}
noPublishedEntities := publishedKnowledgeBases.Entities == nil || len(*publishedKnowledgeBases.Entities) == 0
noUnpublishedEntities := unpublishedKnowledgeBases.Entities == nil || len(*unpublishedKnowledgeBases.Entities) == 0
if noPublishedEntities && noUnpublishedEntities {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("no knowledge bases found with name %s", knowledgeBaseName), publishedResp))
}
// prefer published knowledge base
for _, knowledgeBase := range *publishedKnowledgeBases.Entities {
if knowledgeBase.Name != nil && *knowledgeBase.Name == knowledgeBaseName {
knowledgeCategories, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseCategories(*knowledgeBase.Id, "", "", fmt.Sprintf("%v", pageSize), "", false, name, "", "", false)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to get knowledge category %s | error: %s", name, getErr), resp))
}
for _, knowledgeCategory := range *knowledgeCategories.Entities {
if *knowledgeCategory.Name == name {
id := fmt.Sprintf("%s,%s", *knowledgeCategory.Id, *knowledgeCategory.KnowledgeBase.Id)
d.SetId(id)
return nil
}
}
}
}
// use unpublished knowledge base if unpublished doesn't exist
for _, knowledgeBase := range *unpublishedKnowledgeBases.Entities {
if knowledgeBase.Name != nil && *knowledgeBase.Name == knowledgeBaseName {
knowledgeCategories, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseCategories(*knowledgeBase.Id, "", "", fmt.Sprintf("%v", pageSize), "", false, name, "", "", false)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to get knowledge category %s | error: %s", name, getErr), resp))
}
for _, knowledgeCategory := range *knowledgeCategories.Entities {
if *knowledgeCategory.Name == name {
id := fmt.Sprintf("%s,%s", *knowledgeCategory.Id, *knowledgeCategory.KnowledgeBase.Id)
d.SetId(id)
return nil
}
}
}
}
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceKnowledgeKnowledgebase() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Knowledge Base. Select a knowledge base by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceKnowledgeKnowledgebaseRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Knowledge base name",
Type: schema.TypeString,
Required: true,
},
"core_language": {
Description: "Core language for knowledge base in which initial content must be created, language codes [en-US, en-UK, en-AU, de-DE] are supported currently, however the new DX knowledge will support all these language codes",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"en-US", "en-UK", "en-AU", "de-DE", "es-US", "es-ES", "fr-FR", "pt-BR", "nl-NL", "it-IT", "fr-CA"}, false),
},
},
}
}
func dataSourceKnowledgeKnowledgebaseRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
name := d.Get("name").(string)
coreLanguage := d.Get("core_language").(string)
// Find first non-deleted knowledge base by name. Retry in case new knowledge base is not yet indexed by search
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageSize = 100
publishedKnowledgeBases, publishedResp, getPublishedErr := knowledgeAPI.GetKnowledgeKnowledgebases("", "", "", fmt.Sprintf("%v", pageSize), name, coreLanguage, true, "", "")
unpublishedKnowledgeBases, unpublishedResp, getUnpublishedErr := knowledgeAPI.GetKnowledgeKnowledgebases("", "", "", fmt.Sprintf("%v", pageSize), name, coreLanguage, false, "", "")
if getPublishedErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("error requesting knowledge base %s | error: %s", name, getPublishedErr), publishedResp))
}
if getUnpublishedErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("error requesting knowledge base %s | error: %s", name, getUnpublishedErr), unpublishedResp))
}
noPublishedEntities := publishedKnowledgeBases.Entities == nil || len(*publishedKnowledgeBases.Entities) == 0
noUnpublishedEntities := unpublishedKnowledgeBases.Entities == nil || len(*unpublishedKnowledgeBases.Entities) == 0
if noPublishedEntities && noUnpublishedEntities {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("no knowledge bases found with name %s", name), publishedResp))
}
// prefer published knowledge base
for _, knowledgeBase := range *publishedKnowledgeBases.Entities {
if knowledgeBase.Name != nil && *knowledgeBase.Name == name &&
*knowledgeBase.CoreLanguage == coreLanguage {
d.SetId(*knowledgeBase.Id)
return nil
}
}
// use unpublished knowledge base if unpublished doesn't exist
for _, knowledgeBase := range *unpublishedKnowledgeBases.Entities {
if knowledgeBase.Name != nil && *knowledgeBase.Name == name &&
*knowledgeBase.CoreLanguage == coreLanguage {
d.SetId(*knowledgeBase.Id)
return nil
}
}
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceKnowledgeLabel() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Knowledge Base Label. Select a label by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceKnowledgeLabelRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Knowledge base label name",
Type: schema.TypeString,
Required: true,
},
"knowledge_base_name": {
Description: "Knowledge base name",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceKnowledgeLabelRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
name := d.Get("name").(string)
knowledgeBaseName := d.Get("knowledge_base_name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageSize = 100
publishedKnowledgeBases, publishedResp, getPublishedErr := knowledgeAPI.GetKnowledgeKnowledgebases("", "", "", fmt.Sprintf("%v", pageSize), knowledgeBaseName, "", true, "", "")
unpublishedKnowledgeBases, unpublishedResp, getUnpublishedErr := knowledgeAPI.GetKnowledgeKnowledgebases("", "", "", fmt.Sprintf("%v", pageSize), knowledgeBaseName, "", false, "", "")
if getPublishedErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to get knowledge base %s | error: %s", knowledgeBaseName, getPublishedErr), publishedResp))
}
if getUnpublishedErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to get knowledge base %s | error: %s", knowledgeBaseName, getUnpublishedErr), unpublishedResp))
}
noPublishedEntities := publishedKnowledgeBases.Entities == nil || len(*publishedKnowledgeBases.Entities) == 0
noUnpublishedEntities := unpublishedKnowledgeBases.Entities == nil || len(*unpublishedKnowledgeBases.Entities) == 0
if noPublishedEntities && noUnpublishedEntities {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("no knowledge bases found with name %s", knowledgeBaseName), publishedResp))
}
// prefer published knowledge base
for _, knowledgeBase := range *publishedKnowledgeBases.Entities {
if knowledgeBase.Name != nil && *knowledgeBase.Name == knowledgeBaseName {
knowledgeLabels, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLabels(*knowledgeBase.Id, "", "", fmt.Sprintf("%v", pageSize), name, false)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to get knowledge label %s | error: %s", name, getErr), resp))
}
for _, knowledgeLabel := range *knowledgeLabels.Entities {
if *knowledgeLabel.Name == name {
id := fmt.Sprintf("%s,%s", *knowledgeLabel.Id, *knowledgeBase.Id)
d.SetId(id)
return nil
}
}
}
}
// use unpublished knowledge base if unpublished doesn't exist
for _, knowledgeBase := range *unpublishedKnowledgeBases.Entities {
if knowledgeBase.Name != nil && *knowledgeBase.Name == knowledgeBaseName {
knowledgeLabels, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLabels(*knowledgeBase.Id, "", "", fmt.Sprintf("%v", pageSize), name, false)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to get knowledge label %s | error: %s", name, getErr), resp))
}
for _, knowledgeLabel := range *knowledgeLabels.Entities {
if *knowledgeLabel.Name == name {
id := fmt.Sprintf("%s,%s", *knowledgeLabel.Id, *knowledgeBase.Id)
d.SetId(id)
return nil
}
}
}
}
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceLocation() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Location. Select a location by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceLocationRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Location name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceLocationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
locationsAPI := platformclientv2.NewLocationsApiWithConfig(sdkConfig)
exactSearchType := "EXACT"
nameField := "name"
nameStr := d.Get("name").(string)
searchCriteria := platformclientv2.Locationsearchcriteria{
VarType: &exactSearchType,
Value: &nameStr,
Fields: &[]string{nameField},
}
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
locations, resp, getErr := locationsAPI.PostLocationsSearch(platformclientv2.Locationsearchrequest{
Query: &[]platformclientv2.Locationsearchcriteria{searchCriteria},
})
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_location", fmt.Sprintf("Error requesting location %s | error: %s", nameStr, getErr), resp))
}
if *locations.Total == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_location", fmt.Sprintf("No locations found with search criteria %v ", searchCriteria), resp))
}
// Select first location in the list
location := (*locations.Results)[0]
d.SetId(*location.Id)
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
)
func DataSourceOrganizationsMe() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud current organization",
ReadContext: provider.ReadWithPooledClient(dataSourceOrganizationsMeRead),
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"default_language": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"default_country_code": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"domain": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"default_site_id": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"third_party_org_name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"voicemail_enabled": {
Type: schema.TypeBool,
Computed: true,
Optional: true,
},
"product_platform": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"support_uri": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
},
}
}
func dataSourceOrganizationsMeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
orgAPI := platformclientv2.NewOrganizationApiWithConfig(sdkConfig)
orgMe, resp, getErr := orgAPI.GetOrganizationsMe()
if getErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_organizations_me", fmt.Sprintf("Error requesting organization: %s", getErr), resp)
}
d.SetId(*orgMe.Id)
if orgMe.Name != nil {
d.Set("name", *orgMe.Name)
}
if orgMe.DefaultLanguage != nil {
d.Set("default_language", *orgMe.DefaultLanguage)
}
if orgMe.DefaultCountryCode != nil {
d.Set("default_country_code", *orgMe.DefaultCountryCode)
}
if orgMe.Domain != nil {
d.Set("domain", *orgMe.Domain)
}
if orgMe.DefaultSiteId != nil {
d.Set("default_site_id", *orgMe.DefaultSiteId)
}
if orgMe.ThirdPartyOrgName != nil {
d.Set("third_party_org_name", *orgMe.ThirdPartyOrgName)
}
if orgMe.VoicemailEnabled != nil {
d.Set("voicemail_enabled", *orgMe.VoicemailEnabled)
}
if orgMe.ProductPlatform != nil {
d.Set("product_platform", *orgMe.ProductPlatform)
}
if orgMe.SupportURI != nil {
d.Set("support_uri", *orgMe.SupportURI)
}
return nil
}
func GenerateOrganizationMe() string {
return `
data "genesyscloud_organizations_me" "me" {}
`
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type EvaluationFormQuestionGroupStruct struct {
Name string
DefaultAnswersToHighest bool
DefaultAnswersToNA bool
NaEnabled bool
Weight float32
ManualWeight bool
Questions []EvaluationFormQuestionStruct
VisibilityCondition VisibilityConditionStruct
}
type EvaluationFormStruct struct {
Name string
Published bool
QuestionGroups []EvaluationFormQuestionGroupStruct
}
type EvaluationFormQuestionStruct struct {
Text string
HelpText string
NaEnabled bool
CommentsRequired bool
IsKill bool
IsCritical bool
VisibilityCondition VisibilityConditionStruct
AnswerOptions []AnswerOptionStruct
}
type AnswerOptionStruct struct {
Text string
Value int
}
type VisibilityConditionStruct struct {
CombiningOperation string
Predicates []string
}
func DataSourceQualityFormsEvaluations() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Evaluation Forms. Select an evaluations form by name",
ReadContext: provider.ReadWithPooledClient(dataSourceQualityFormsEvaluationsRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Evaluation Form name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceQualityFormsEvaluationsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
form, resp, getErr := qualityAPI.GetQualityForms(pageSize, pageNum, "", "", "", "", name, "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Error requesting evaluation form %s | error: %s", name, getErr), resp))
}
if form.Entities == nil || len(*form.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("No evaluation form found with name %s", name), resp))
}
d.SetId(*(*form.Entities)[0].Id)
return nil
}
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceQualityFormsSurvey() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud survey form. Select a form by name",
ReadContext: provider.ReadWithPooledClient(dataSourceQualityFormsSurveyRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Survey form name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceQualityFormsSurveyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
forms, resp, getErr := qualityAPI.GetQualityFormsSurveys(pageSize, pageNum, "", "", "", "", name, "desc")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Error requesting survey forms %s | error: %s", name, getErr), resp))
}
if forms.Entities == nil || len(*forms.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("No survey forms found with name %s", name), resp))
}
d.SetId(*(*forms.Entities)[0].Id)
return nil
}
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// Returns the schema for the routing email domain
func DataSourceRoutingEmailDomain() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Email Domains. Select an email domain by name",
ReadContext: provider.ReadWithPooledClient(DataSourceRoutingEmailDomainRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Email domain name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
// Looks up the data for the Email Domain
func DataSourceRoutingEmailDomainRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
domains, resp, getErr := routingAPI.GetRoutingEmailDomains(pageSize, pageNum, false, "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Error requesting email domain %s | error: %s", name, getErr), resp))
}
//// No record found, keep trying for X seconds as this might an eventual consistency problem
if domains.Entities == nil || len(*domains.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("No email domains found with name %s", name), resp))
}
// Once I get a result, cycle through until we find a name that matches
for _, domain := range *domains.Entities {
if domain.Id != nil && *domain.Id == name {
d.SetId(*domain.Id)
return nil
}
}
}
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceRoutingLanguage() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Routing Languages. Select a language by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingLanguageRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Language name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceRoutingLanguageRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
name := d.Get("name").(string)
// Find first non-deleted language by name. Retry in case new language is not yet indexed by search
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 50
languages, resp, getErr := routingAPI.GetRoutingLanguages(pageSize, pageNum, "", name, nil)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Error requesting language %s | error: %s", name, getErr), resp))
}
if languages.Entities == nil || len(*languages.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("No routing languages found with name %s", name), resp))
}
for _, language := range *languages.Entities {
if language.Name != nil && *language.Name == name &&
language.State != nil && *language.State != "deleted" {
d.SetId(*language.Id)
return nil
}
}
}
})
}
package genesyscloud
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
)
func dataSourceRoutingSettings() *schema.Resource {
return &schema.Resource{
Description: "An organization's routing settings",
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingSettingsRead),
SchemaVersion: 1,
Schema: ResourceRoutingSettings().Schema,
}
}
func dataSourceRoutingSettingsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
settings, resp, getErr := routingAPI.GetRoutingSettings()
if getErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Error requesting routing settings error: %s", getErr), resp)
}
d.SetId("datasource-settings")
if settings.ResetAgentScoreOnPresenceChange != nil {
d.Set("reset_agent_on_presence_change", *settings.ResetAgentScoreOnPresenceChange)
}
if diagErr := readRoutingSettingsContactCenter(d, routingAPI); diagErr != nil {
return util.BuildDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Error reading routing settings contact center"), fmt.Errorf("%v", diagErr))
}
if diagErr := readRoutingSettingsTranscription(d, routingAPI); diagErr != nil {
return util.BuildDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Error reading routing settings transcription"), fmt.Errorf("%v", diagErr))
}
return nil
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceRoutingSkill() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Routing Skills. Select a skill by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingSkillRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Skill name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceRoutingSkillRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
const pageSize = 100
var pageCount int
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
name := d.Get("name").(string)
skills, resp, getErr := routingAPI.GetRoutingSkills(pageSize, 1, name, nil)
if getErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Error requesting skills error: %s", getErr), resp)
}
pageCount = *skills.PageCount
// Find first non-deleted skill by name. Retry in case new skill is not yet indexed by search
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
skills, resp, getErr := routingAPI.GetRoutingSkills(pageSize, pageNum, name, nil)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("error requesting skill %s | error: %s", name, getErr), resp))
}
if skills.Entities == nil || len(*skills.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("no routing skills found with name %s", name), resp))
}
for _, skill := range *skills.Entities {
if skill.Name != nil && *skill.Name == name &&
skill.State != nil && *skill.State != "deleted" {
d.SetId(*skill.Id)
return nil
}
}
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("no routing skills found with name %s", name), resp))
})
}
package genesyscloud
import (
"context"
"encoding/json"
"fmt"
"net/http"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceRoutingSkillGroup() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Routing Skills Groups. Select a skill group by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingSkillGroupRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Skill group name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceRoutingSkillGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
name := d.Get("name").(string)
// Find first non-deleted skill by name. Retry in case new skill is not yet indexed by search
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
apiClient := &routingAPI.Configuration.APIClient
path := routingAPI.Configuration.BasePath + "/api/v2/routing/skillgroups"
headerParams := buildHeaderParams(routingAPI)
response, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil)
if err != nil {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("error encountered while trying to retrieve routing skills group found with name %s | error: %s", name, err), response))
}
if err == nil && response.Error != nil {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("error encountered while trying to retrieve routing skills group found with name %s | error:%s", name, err), response))
}
if err == nil && response.StatusCode == http.StatusNotFound {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("routing skills group not found with name %s", name), response))
}
allSkillGroups := &AllSkillGroups{}
err = json.Unmarshal(response.RawBody, &allSkillGroups)
if err != nil {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("error encountered while trying to retrieve routing skills group found with name %s %s", name, err), response))
}
if allSkillGroups.Entities == nil || len(allSkillGroups.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("no routing skills groups found with name %s", name), response))
}
for _, skillGroup := range allSkillGroups.Entities {
if skillGroup.Name == name {
d.SetId(skillGroup.ID)
return nil
}
}
}
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceRoutingUtilizationLabel() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Routing Utilization Labels. Select a label by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingUtilizationLabelRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Label name.",
Type: schema.TypeString,
ValidateFunc: validation.StringDoesNotContainAny("*"),
Required: true,
},
},
}
}
func dataSourceRoutingUtilizationLabelRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
labels, resp, getErr := routingAPI.GetRoutingUtilizationLabels(1, 1, "", name)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Error requesting label %s | error: %s", name, getErr), resp))
}
if labels.Entities == nil || len(*labels.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("No labels found with name %s", name), resp))
}
label := (*labels.Entities)[0]
d.SetId(*label.Id)
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceRoutingWrapupcode() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Wrap-up Code. Select a wrap-up code by name",
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingWrapupcodeRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Wrap-up code name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceRoutingWrapupcodeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
wrapCode, resp, getErr := routingAPI.GetRoutingWrapupcodes(100, pageNum, "", "", name, []string{}, []string{})
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Error requesting wrap-up code %s | error: %s", name, getErr), resp))
}
if wrapCode.Entities == nil || len(*wrapCode.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("No wrap-up code found with name %s", name), resp))
}
d.SetId(*(*wrapCode.Entities)[0].Id)
return nil
}
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceUser() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Users. Select a user by email or name.",
ReadContext: provider.ReadWithPooledClient(DataSourceUserRead),
Schema: map[string]*schema.Schema{
"email": {
Description: "User email.",
Type: schema.TypeString,
Optional: true,
},
"name": {
Description: "User name.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
func DataSourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig)
exactSearchType := "EXACT"
sortOrderAsc := "ASC"
emailField := "email"
nameField := "name"
searchCriteria := platformclientv2.Usersearchcriteria{
VarType: &exactSearchType,
}
if email, ok := d.GetOk("email"); ok {
emailStr := email.(string)
searchCriteria.Fields = &[]string{emailField}
searchCriteria.Value = &emailStr
} else if name, ok := d.GetOk("name"); ok {
nameStr := name.(string)
searchCriteria.Fields = &[]string{nameField}
searchCriteria.Value = &nameStr
} else {
return util.BuildDiagnosticError("genesyscloud_user", fmt.Sprintf("No user search field specified"), fmt.Errorf("no user search field specified"))
}
// Retry in case user is not yet indexed
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
users, resp, getErr := usersAPI.PostUsersSearch(platformclientv2.Usersearchrequest{
SortBy: &emailField,
SortOrder: &sortOrderAsc,
Query: &[]platformclientv2.Usersearchcriteria{searchCriteria},
})
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("Error requesting users: %s", getErr), resp))
}
if users.Results == nil || len(*users.Results) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("No users found with search criteria %v", searchCriteria), resp))
}
// Select first user in the list
user := (*users.Results)[0]
d.SetId(*user.Id)
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceWidgetDeployments() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Widget Deployment. Select a widget deployment.",
ReadContext: provider.ReadWithPooledClient(dataSourceWidgetDeploymentRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Widget Deployment Name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceWidgetDeploymentRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
widgetAPI := platformclientv2.NewWidgetsApiWithConfig(sdkConfig)
name := d.Get("name").(string)
// Query widget by name. Retry in case search has not yet indexed the widget.
return util.WithRetries(ctx, 5*time.Second, func() *retry.RetryError {
widgetDeployments, resp, getErr := widgetAPI.GetWidgetsDeployments()
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Error requesting widget deployment %s | error: %s", name, getErr), resp))
}
if widgetDeployments.Entities == nil || len(*widgetDeployments.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("No widget deployment found with name %s", name), resp))
}
for _, widgetDeployment := range *widgetDeployments.Entities {
if *widgetDeployment.Name == name {
d.SetId(*widgetDeployment.Id)
return nil
}
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Unable to locate widget deployment name %s. It does not exist", name), resp))
})
}
package employeeperformance_externalmetrics_definitions
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_employeeperformance_externalmetrics_definition.go contains the data source implementation
for the resource.
*/
// dataSourceEmployeeperformanceExternalmetricsDefinitionRead retrieves by name the id in question
func dataSourceEmployeeperformanceExternalmetricsDefinitionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newEmployeeperformanceExternalmetricsDefinitionProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
domainOrganizationRoleId, retryable, resp, err := proxy.getEmployeeperformanceExternalmetricsDefinitionIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching employeeperformance externalmetrics definition %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No employeeperformance externalmetrics definition found with name %s", name), resp))
}
d.SetId(domainOrganizationRoleId)
return nil
})
}
package employeeperformance_externalmetrics_definitions
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
)
/*
The genesyscloud_employeeperformance_externalmetrics_definition_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *employeeperformanceExternalmetricsDefinitionProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createEmployeeperformanceExternalmetricsDefinitionFunc func(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, domainOrganizationRole *platformclientv2.Externalmetricdefinitioncreaterequest) (*platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error)
type getAllEmployeeperformanceExternalmetricsDefinitionFunc func(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy) (*[]platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error)
type getEmployeeperformanceExternalmetricsDefinitionIdByNameFunc func(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getEmployeeperformanceExternalmetricsDefinitionByIdFunc func(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, id string) (domainOrganizationRole *platformclientv2.Externalmetricdefinition, response *platformclientv2.APIResponse, err error)
type updateEmployeeperformanceExternalmetricsDefinitionFunc func(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, id string, domainOrganizationRole *platformclientv2.Externalmetricdefinitionupdaterequest) (*platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error)
type deleteEmployeeperformanceExternalmetricsDefinitionFunc func(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, id string) (response *platformclientv2.APIResponse, err error)
// employeeperformanceExternalmetricsDefinitionProxy contains all of the methods that call genesys cloud APIs.
type employeeperformanceExternalmetricsDefinitionProxy struct {
clientConfig *platformclientv2.Configuration
gamificationApi *platformclientv2.GamificationApi
createEmployeeperformanceExternalmetricsDefinitionAttr createEmployeeperformanceExternalmetricsDefinitionFunc
getAllEmployeeperformanceExternalmetricsDefinitionAttr getAllEmployeeperformanceExternalmetricsDefinitionFunc
getEmployeeperformanceExternalmetricsDefinitionIdByNameAttr getEmployeeperformanceExternalmetricsDefinitionIdByNameFunc
getEmployeeperformanceExternalmetricsDefinitionByIdAttr getEmployeeperformanceExternalmetricsDefinitionByIdFunc
updateEmployeeperformanceExternalmetricsDefinitionAttr updateEmployeeperformanceExternalmetricsDefinitionFunc
deleteEmployeeperformanceExternalmetricsDefinitionAttr deleteEmployeeperformanceExternalmetricsDefinitionFunc
}
// newEmployeeperformanceExternalmetricsDefinitionProxy initializes the employeeperformance externalmetrics definition proxy with all of the data needed to communicate with Genesys Cloud
func newEmployeeperformanceExternalmetricsDefinitionProxy(clientConfig *platformclientv2.Configuration) *employeeperformanceExternalmetricsDefinitionProxy {
api := platformclientv2.NewGamificationApiWithConfig(clientConfig)
return &employeeperformanceExternalmetricsDefinitionProxy{
clientConfig: clientConfig,
gamificationApi: api,
createEmployeeperformanceExternalmetricsDefinitionAttr: createEmployeeperformanceExternalmetricsDefinitionFn,
getAllEmployeeperformanceExternalmetricsDefinitionAttr: getAllEmployeeperformanceExternalmetricsDefinitionFn,
getEmployeeperformanceExternalmetricsDefinitionIdByNameAttr: getEmployeeperformanceExternalmetricsDefinitionIdByNameFn,
getEmployeeperformanceExternalmetricsDefinitionByIdAttr: getEmployeeperformanceExternalmetricsDefinitionByIdFn,
updateEmployeeperformanceExternalmetricsDefinitionAttr: updateEmployeeperformanceExternalmetricsDefinitionFn,
deleteEmployeeperformanceExternalmetricsDefinitionAttr: deleteEmployeeperformanceExternalmetricsDefinitionFn,
}
}
// getEmployeeperformanceExternalmetricsDefinitionProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getEmployeeperformanceExternalmetricsDefinitionProxy(clientConfig *platformclientv2.Configuration) *employeeperformanceExternalmetricsDefinitionProxy {
if internalProxy == nil {
internalProxy = newEmployeeperformanceExternalmetricsDefinitionProxy(clientConfig)
}
return internalProxy
}
// createEmployeeperformanceExternalmetricsDefinition creates a Genesys Cloud employeeperformance externalmetrics definition
func (p *employeeperformanceExternalmetricsDefinitionProxy) createEmployeeperformanceExternalmetricsDefinition(ctx context.Context, employeeperformanceExternalmetricsDefinition *platformclientv2.Externalmetricdefinitioncreaterequest) (*platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error) {
return p.createEmployeeperformanceExternalmetricsDefinitionAttr(ctx, p, employeeperformanceExternalmetricsDefinition)
}
// getEmployeeperformanceExternalmetricsDefinition retrieves all Genesys Cloud employeeperformance externalmetrics definition
func (p *employeeperformanceExternalmetricsDefinitionProxy) getAllEmployeeperformanceExternalmetricsDefinition(ctx context.Context) (*[]platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error) {
return p.getAllEmployeeperformanceExternalmetricsDefinitionAttr(ctx, p)
}
// getEmployeeperformanceExternalmetricsDefinitionIdByName returns a single Genesys Cloud employeeperformance externalmetrics definition by a name
func (p *employeeperformanceExternalmetricsDefinitionProxy) getEmployeeperformanceExternalmetricsDefinitionIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getEmployeeperformanceExternalmetricsDefinitionIdByNameAttr(ctx, p, name)
}
// getEmployeeperformanceExternalmetricsDefinitionById returns a single Genesys Cloud employeeperformance externalmetrics definition by Id
func (p *employeeperformanceExternalmetricsDefinitionProxy) getEmployeeperformanceExternalmetricsDefinitionById(ctx context.Context, id string) (employeeperformanceExternalmetricsDefinition *platformclientv2.Externalmetricdefinition, response *platformclientv2.APIResponse, err error) {
return p.getEmployeeperformanceExternalmetricsDefinitionByIdAttr(ctx, p, id)
}
// updateEmployeeperformanceExternalmetricsDefinition updates a Genesys Cloud employeeperformance externalmetrics definition
func (p *employeeperformanceExternalmetricsDefinitionProxy) updateEmployeeperformanceExternalmetricsDefinition(ctx context.Context, id string, employeeperformanceExternalmetricsDefinition *platformclientv2.Externalmetricdefinitionupdaterequest) (*platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error) {
return p.updateEmployeeperformanceExternalmetricsDefinitionAttr(ctx, p, id, employeeperformanceExternalmetricsDefinition)
}
// deleteEmployeeperformanceExternalmetricsDefinition deletes a Genesys Cloud employeeperformance externalmetrics definition by Id
func (p *employeeperformanceExternalmetricsDefinitionProxy) deleteEmployeeperformanceExternalmetricsDefinition(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteEmployeeperformanceExternalmetricsDefinitionAttr(ctx, p, id)
}
// createEmployeeperformanceExternalmetricsDefinitionFn is an implementation function for creating a Genesys Cloud employeeperformance externalmetrics definition
func createEmployeeperformanceExternalmetricsDefinitionFn(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, employeeperformanceExternalmetricsDefinition *platformclientv2.Externalmetricdefinitioncreaterequest) (*platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error) {
definition, resp, err := p.gamificationApi.PostEmployeeperformanceExternalmetricsDefinitions(*employeeperformanceExternalmetricsDefinition)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create employeeperformance externalmetrics definition: %s", err)
}
return definition, resp, nil
}
// getAllEmployeeperformanceExternalmetricsDefinitionFn is the implementation for retrieving all employeeperformance externalmetrics definition in Genesys Cloud
func getAllEmployeeperformanceExternalmetricsDefinitionFn(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy) (*[]platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error) {
var allDefinitions []platformclientv2.Externalmetricdefinition
const pageSize = 100
definitions, resp, err := p.gamificationApi.GetEmployeeperformanceExternalmetricsDefinitions(pageSize, 1)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get domain organization role: %v", err)
}
if definitions.Entities == nil || len(*definitions.Entities) == 0 {
return &allDefinitions, resp, nil
}
for _, definition := range *definitions.Entities {
allDefinitions = append(allDefinitions, definition)
}
for pageNum := 2; pageNum <= *definitions.PageCount; pageNum++ {
definitions, resp, err := p.gamificationApi.GetEmployeeperformanceExternalmetricsDefinitions(pageSize, pageNum)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get domain organization role: %v", err)
}
if definitions.Entities == nil || len(*definitions.Entities) == 0 {
break
}
for _, definition := range *definitions.Entities {
allDefinitions = append(allDefinitions, definition)
}
}
return &allDefinitions, resp, nil
}
// getEmployeeperformanceExternalmetricsDefinitionIdByNameFn is an implementation of the function to get a Genesys Cloud employeeperformance externalmetrics definition by name
func getEmployeeperformanceExternalmetricsDefinitionIdByNameFn(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
definitions, resp, err := getAllEmployeeperformanceExternalmetricsDefinitionFn(ctx, p)
if err != nil {
return "", false, resp, err
}
if definitions == nil || len(*definitions) == 0 {
return "", true, resp, fmt.Errorf("No employeeperformance externalmetrics definition found with name %s", name)
}
for _, definition := range *definitions {
if *definition.Name == name {
log.Printf("Retrieved the employeeperformance externalmetrics definition id %s by name %s", *definition.Id, name)
return *definition.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find employeeperformance externalmetrics definition with name %s", name)
}
// getEmployeeperformanceExternalmetricsDefinitionByIdFn is an implementation of the function to get a Genesys Cloud employeeperformance externalmetrics definition by Id
func getEmployeeperformanceExternalmetricsDefinitionByIdFn(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, id string) (employeeperformanceExternalmetricsDefinition *platformclientv2.Externalmetricdefinition, response *platformclientv2.APIResponse, err error) {
definition, resp, err := p.gamificationApi.GetEmployeeperformanceExternalmetricsDefinition(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve employeeperformance externalmetrics definition by id %s: %s", id, err)
}
return definition, resp, nil
}
// updateEmployeeperformanceExternalmetricsDefinitionFn is an implementation of the function to update a Genesys Cloud employeeperformance externalmetrics definition
func updateEmployeeperformanceExternalmetricsDefinitionFn(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, id string, employeeperformanceExternalmetricsDefinition *platformclientv2.Externalmetricdefinitionupdaterequest) (*platformclientv2.Externalmetricdefinition, *platformclientv2.APIResponse, error) {
definition, resp, err := p.gamificationApi.PatchEmployeeperformanceExternalmetricsDefinition(id, *employeeperformanceExternalmetricsDefinition)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update employeeperformance externalmetrics definition: %s", err)
}
return definition, resp, nil
}
// deleteEmployeeperformanceExternalmetricsDefinitionFn is an implementation function for deleting a Genesys Cloud employeeperformance externalmetrics definition
func deleteEmployeeperformanceExternalmetricsDefinitionFn(ctx context.Context, p *employeeperformanceExternalmetricsDefinitionProxy, id string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.gamificationApi.DeleteEmployeeperformanceExternalmetricsDefinition(id)
if err != nil {
return resp, fmt.Errorf("Failed to delete employeeperformance externalmetrics definition: %s", err)
}
return resp, nil
}
package employeeperformance_externalmetrics_definitions
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_employeeperformance_externalmetrics_definition.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthEmployeeperformanceExternalmetricsDefinition retrieves all of the employeeperformance externalmetrics definition via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthEmployeeperformanceExternalmetricsDefinitions(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := newEmployeeperformanceExternalmetricsDefinitionProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
definitions, resp, err := proxy.getAllEmployeeperformanceExternalmetricsDefinition(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get employeeperformance externalmetrics definition error: %s", err), resp)
}
for _, definition := range *definitions {
resources[*definition.Id] = &resourceExporter.ResourceMeta{Name: *definition.Name}
}
return resources, nil
}
// createEmployeeperformanceExternalmetricsDefinition is used by the employeeperformance_externalmetrics_definition resource to create Genesys cloud employeeperformance externalmetrics definition
func createEmployeeperformanceExternalmetricsDefinition(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getEmployeeperformanceExternalmetricsDefinitionProxy(sdkConfig)
metricDefinition := platformclientv2.Externalmetricdefinitioncreaterequest{
Name: platformclientv2.String(d.Get("name").(string)),
Unit: platformclientv2.String(d.Get("unit").(string)),
Enabled: platformclientv2.Bool(d.Get("enabled").(bool)),
Precision: platformclientv2.Int(d.Get("precision").(int)),
DefaultObjectiveType: platformclientv2.String(d.Get("default_objective_type").(string)),
}
unitDefinition := d.Get("unit_definition").(string)
if unitDefinition != "" {
metricDefinition.UnitDefinition = &unitDefinition
}
log.Printf("Creating employeeperformance externalmetrics definition %s", *metricDefinition.Name)
definition, resp, err := proxy.createEmployeeperformanceExternalmetricsDefinition(ctx, &metricDefinition)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create employeeperformance externalmetrics definition %s error: %s", *metricDefinition.Name, err), resp)
}
d.SetId(*definition.Id)
log.Printf("Created employeeperformance externalmetrics definition %s: %s", *definition.Name, *definition.Id)
return readEmployeeperformanceExternalmetricsDefinition(ctx, d, meta)
}
// readEmployeeperformanceExternalmetricsDefinition is used by the employeeperformance_externalmetrics_definition resource to read an employeeperformance externalmetrics definition from genesys cloud
func readEmployeeperformanceExternalmetricsDefinition(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getEmployeeperformanceExternalmetricsDefinitionProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceEmployeeperformanceExternalmetricsDefinition(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading employeeperformance externalmetrics definition %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
definition, resp, getErr := proxy.getEmployeeperformanceExternalmetricsDefinitionById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read employeeperformance externalmetrics definition %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read employeeperformance externalmetrics definition %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", definition.Name)
resourcedata.SetNillableValue(d, "precision", definition.Precision)
resourcedata.SetNillableValue(d, "default_objective_type", definition.DefaultObjectiveType)
resourcedata.SetNillableValue(d, "enabled", definition.Enabled)
resourcedata.SetNillableValue(d, "unit", definition.Unit)
resourcedata.SetNillableValue(d, "unit_definition", definition.UnitDefinition)
log.Printf("Read employeeperformance externalmetrics definition %s %s", d.Id(), *definition.Name)
return cc.CheckState(d)
})
}
// updateEmployeeperformanceExternalmetricsDefinition is used by the employeeperformance_externalmetrics_definition resource to update an employeeperformance externalmetrics definition in Genesys Cloud
func updateEmployeeperformanceExternalmetricsDefinition(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getEmployeeperformanceExternalmetricsDefinitionProxy(sdkConfig)
metricDefinition := platformclientv2.Externalmetricdefinitionupdaterequest{
Name: platformclientv2.String(d.Get("name").(string)),
Enabled: platformclientv2.Bool(d.Get("enabled").(bool)),
Precision: platformclientv2.Int(d.Get("precision").(int)),
DefaultObjectiveType: platformclientv2.String(d.Get("default_objective_type").(string)),
}
log.Printf("Updating employeeperformance externalmetrics definition %s: %s", *metricDefinition.Name, d.Id())
definition, resp, err := proxy.updateEmployeeperformanceExternalmetricsDefinition(ctx, d.Id(), &metricDefinition)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update employeeperformance externalmetrics definition %s error: %s", *metricDefinition.Name, err), resp)
}
log.Printf("Updated employeeperformance externalmetrics definition %s", *definition.Id)
return readEmployeeperformanceExternalmetricsDefinition(ctx, d, meta)
}
// deleteEmployeeperformanceExternalmetricsDefinition is used by the employeeperformance_externalmetrics_definition resource to delete an employeeperformance externalmetrics definition from Genesys cloud
func deleteEmployeeperformanceExternalmetricsDefinition(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getEmployeeperformanceExternalmetricsDefinitionProxy(sdkConfig)
resp, err := proxy.deleteEmployeeperformanceExternalmetricsDefinition(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete employeeperformance externalmetrics definition %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getEmployeeperformanceExternalmetricsDefinitionById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted employeeperformance externalmetrics definition %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting employeeperformance externalmetrics definition %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("employeeperformance externalmetrics definition %s still exists", d.Id()), resp))
})
}
package employeeperformance_externalmetrics_definitions
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_employeeperformance_externalmetrics_definition_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the employeeperformance_externalmetrics_definition resource.
3. The datasource schema definitions for the employeeperformance_externalmetrics_definition datasource.
4. The resource exporter configuration for the employeeperformance_externalmetrics_definition exporter.
*/
const resourceName = "genesyscloud_employeeperformance_externalmetrics_definitions"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceEmployeeperformanceExternalmetricsDefinition())
regInstance.RegisterDataSource(resourceName, DataSourceEmployeeperformanceExternalmetricsDefinition())
regInstance.RegisterExporter(resourceName, EmployeeperformanceExternalmetricsDefinitionExporter())
}
// ResourceEmployeeperformanceExternalmetricsDefinition registers the genesyscloud_employeeperformance_externalmetrics_definitions resource with Terraform
func ResourceEmployeeperformanceExternalmetricsDefinition() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud employeeperformance externalmetrics definition`,
CreateContext: provider.CreateWithPooledClient(createEmployeeperformanceExternalmetricsDefinition),
ReadContext: provider.ReadWithPooledClient(readEmployeeperformanceExternalmetricsDefinition),
UpdateContext: provider.UpdateWithPooledClient(updateEmployeeperformanceExternalmetricsDefinition),
DeleteContext: provider.DeleteWithPooledClient(deleteEmployeeperformanceExternalmetricsDefinition),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the External Metric Definition`,
Required: true,
Type: schema.TypeString,
},
`precision`: {
Description: `The decimal precision of the External Metric Definition. Must be at least 0 and at most 5`,
Required: true,
Type: schema.TypeInt,
ValidateFunc: validation.IntBetween(0, 5),
},
`default_objective_type`: {
Description: `The default objective type of the External Metric Definition`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`HigherIsBetter`, `LowerIsBetter`, `TargetArea`}, false),
},
`enabled`: {
Description: `True if the External Metric Definition is enabled`,
Required: true,
Type: schema.TypeBool,
},
`unit`: {
Description: `The unit of the External Metric Definition. Note: Changing the unit property will cause the external metric object to be dropped and recreated with a new ID.`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`Seconds`, `Percent`, `Number`, `Currency`}, false),
},
`unit_definition`: {
Description: `The unit definition of the External Metric Definition. Note: Changing the unit definition property will cause the external metric object to be dropped and recreated with a new ID.`,
Optional: true,
ForceNew: true,
Type: schema.TypeString,
},
},
}
}
// EmployeeperformanceExternalmetricsDefinitionExporter returns the resourceExporter object used to hold the genesyscloud_employeeperformance_externalmetrics_definition exporter's config
func EmployeeperformanceExternalmetricsDefinitionExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthEmployeeperformanceExternalmetricsDefinitions),
AllowZeroValues: []string{"precision"},
}
}
// DataSourceEmployeeperformanceExternalmetricsDefinition registers the genesyscloud_employeeperformance_externalmetrics_definition data source
func DataSourceEmployeeperformanceExternalmetricsDefinition() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Employeeperformance Externalmetrics Definition. Select a Employeeperformance Externalmetrics Definition by name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceEmployeeperformanceExternalmetricsDefinitionRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Employeeperformance Externalmetrics Definition name.`,
Type: schema.TypeString,
Optional: true,
},
},
}
}
package external_contacts
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_externalcontacts_contact.go contains the data source implementation
for the resource.
Note: This code should contain no code for doing the actual lookup in Genesys Cloud. Instead,
it should be added to the _proxy.go file for the class using our proxy pattern.
*/
// dataSourceExternalContactsContactRead retrieves by search term the id in question
func dataSourceExternalContactsContactRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
ep := newExternalContactsContactsProxy(sdkConfig)
search := d.Get("search").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
contactId, retryable, resp, err := ep.getExternalContactIdBySearch(ctx, search)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching exteral contact %s | error: %s", search, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No external contact found with search %s", search), resp))
}
d.SetId(contactId)
return nil
})
}
package external_contacts
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_externalcontacts_contact_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *externalContactsContactsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllExternalContactsFunc func(ctx context.Context, p *externalContactsContactsProxy) (*[]platformclientv2.Externalcontact, *platformclientv2.APIResponse, error)
type createExternalContactFunc func(ctx context.Context, p *externalContactsContactsProxy, externalContact *platformclientv2.Externalcontact) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error)
type deleteExternalContactFunc func(ctx context.Context, p *externalContactsContactsProxy, externalContactId string) (response *platformclientv2.APIResponse, err error)
type getExternalContactByIdFunc func(ctx context.Context, p *externalContactsContactsProxy, externalContactId string) (externalContact *platformclientv2.Externalcontact, response *platformclientv2.APIResponse, err error)
type getExternalContactIdBySearchFunc func(ctx context.Context, p *externalContactsContactsProxy, search string) (externalContactId string, retryable bool, response *platformclientv2.APIResponse, err error)
type updateExternalContactFunc func(ctx context.Context, p *externalContactsContactsProxy, externalContactId string, externalContact *platformclientv2.Externalcontact) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error)
// externalContactsContactsProxy contains all of the methods that call genesys cloud APIs.
type externalContactsContactsProxy struct {
clientConfig *platformclientv2.Configuration
externalContactsApi *platformclientv2.ExternalContactsApi
getAllExternalContactsAttr getAllExternalContactsFunc
createExternalContactAttr createExternalContactFunc
deleteExternalContactByIdAttr deleteExternalContactFunc
getExternalContactByIdAttr getExternalContactByIdFunc
getExternalContactIdBySearchAttr getExternalContactIdBySearchFunc
updateExternalContactAttr updateExternalContactFunc
}
// newExternalContactsContactsProxy initializes the External Contacts proxy with all of the data needed to communicate with Genesys Cloud
func newExternalContactsContactsProxy(clientConfig *platformclientv2.Configuration) *externalContactsContactsProxy {
api := platformclientv2.NewExternalContactsApiWithConfig(clientConfig)
return &externalContactsContactsProxy{
clientConfig: clientConfig,
externalContactsApi: api,
getAllExternalContactsAttr: getAllExternalContactsFn,
createExternalContactAttr: createExternalContactFn,
getExternalContactByIdAttr: getExternalContactByIdFn,
deleteExternalContactByIdAttr: deleteExternalContactsFn,
getExternalContactIdBySearchAttr: getExternalContactIdBySearchFn,
updateExternalContactAttr: updateExternalContactFn,
}
}
// getExternalContactsContactsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getExternalContactsContactsProxy(clientConfig *platformclientv2.Configuration) *externalContactsContactsProxy {
if internalProxy == nil {
internalProxy = newExternalContactsContactsProxy(clientConfig)
}
return internalProxy
}
// getAllExternalContacts retrieves all Genesys Cloud External Contacts
func (p *externalContactsContactsProxy) getAllExternalContacts(ctx context.Context) (*[]platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
return p.getAllExternalContactsAttr(ctx, p)
}
// createExternalContact creates a Genesys Cloud External Contact
func (p *externalContactsContactsProxy) createExternalContact(ctx context.Context, externalContact *platformclientv2.Externalcontact) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
return p.createExternalContactAttr(ctx, p, externalContact)
}
// DeleteExternalContact deletes a Genesys Cloud External Contact by Id
func (p *externalContactsContactsProxy) deleteExternalContactId(ctx context.Context, externalContactId string) (*platformclientv2.APIResponse, error) {
return p.deleteExternalContactByIdAttr(ctx, p, externalContactId)
}
// getExternalContactById returns a single Genesys Cloud External Contact by Id
func (p *externalContactsContactsProxy) getExternalContactById(ctx context.Context, externalContactId string) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
return p.getExternalContactByIdAttr(ctx, p, externalContactId)
}
// getExternalContactIdBySearch returns a single Genesys Cloud External Contact by a search term
func (p *externalContactsContactsProxy) getExternalContactIdBySearch(ctx context.Context, search string) (externalContactId string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getExternalContactIdBySearchAttr(ctx, p, search)
}
// updateExternalContact updates a Genesys Cloud External Contact
func (p *externalContactsContactsProxy) updateExternalContact(ctx context.Context, externalContactId string, externalContact *platformclientv2.Externalcontact) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
return p.updateExternalContactAttr(ctx, p, externalContactId, externalContact)
}
// getAllExternalContactsFn is the implementation for retrieving all external contacts in Genesys Cloud
func getAllExternalContactsFn(ctx context.Context, p *externalContactsContactsProxy) (*[]platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
var allExternalContacts []platformclientv2.Externalcontact
cursor := ""
var response *platformclientv2.APIResponse
for {
externalContacts, resp, err := p.externalContactsApi.GetExternalcontactsScanContacts(100, cursor)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get external contacts: %v", err)
}
response = resp
if externalContacts.Entities == nil || len(*externalContacts.Entities) == 0 {
break
}
for _, externalContact := range *externalContacts.Entities {
allExternalContacts = append(allExternalContacts, externalContact)
}
if externalContacts.Cursors == nil || externalContacts.Cursors.After == nil {
break
}
cursor = *externalContacts.Cursors.After
}
return &allExternalContacts, response, nil
}
// createExternalContactFn is an implementation function for creating a Genesys Cloud External Contact
func createExternalContactFn(ctx context.Context, p *externalContactsContactsProxy, externalContact *platformclientv2.Externalcontact) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
contact, resp, err := p.externalContactsApi.PostExternalcontactsContacts(*externalContact)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create external contact: %s", err)
}
return contact, resp, nil
}
// deleteExternalContactsFn is an implementation function for deleting a Genesys Cloud External Contact
func deleteExternalContactsFn(ctx context.Context, p *externalContactsContactsProxy, externalContactId string) (*platformclientv2.APIResponse, error) {
_, resp, err := p.externalContactsApi.DeleteExternalcontactsContact(externalContactId)
if err != nil {
return resp, fmt.Errorf("Failed to delete external contact: %s", err)
}
return resp, nil
}
// getExternalContactByIdFn is an implementation of the function to get a Genesys Cloud External Contact by Id
func getExternalContactByIdFn(ctx context.Context, p *externalContactsContactsProxy, externalContactId string) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
externalContact, resp, err := p.externalContactsApi.GetExternalcontactsContact(externalContactId, nil)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve external contact by id %s: %s", externalContactId, err)
}
return externalContact, resp, nil
}
// getExternalContactIdBySearchFn is an implementation of the function to get a Genesys Cloud External contact by a search team
func getExternalContactIdBySearchFn(ctx context.Context, p *externalContactsContactsProxy, search string) (externalContactId string, retryable bool, response *platformclientv2.APIResponse, err error) {
const pageNum = 1
const pageSize = 100
contacts, resp, err := p.externalContactsApi.GetExternalcontactsContacts(pageSize, pageNum, search, "", nil)
if err != nil {
return "", false, resp, fmt.Errorf("Error searching external contact %s: %s", search, err)
}
if contacts.Entities == nil || len(*contacts.Entities) == 0 {
return "", true, resp, fmt.Errorf("No external contact found with search %s", search)
}
if len(*contacts.Entities) > 1 {
return "", false, resp, fmt.Errorf("Too many values returned in look for external contact. Unable to choose 1 external contact. Please refine search and continue.")
}
log.Printf("Retrieved the external contact search id %s by name %s", *(*contacts.Entities)[0].Id, search)
contact := (*contacts.Entities)[0]
return *contact.Id, false, resp, nil
}
// updateExternalContactFn is an implementation of the function to update a Genesys Cloud external contact
func updateExternalContactFn(ctx context.Context, p *externalContactsContactsProxy, externalContactId string, externalContact *platformclientv2.Externalcontact) (*platformclientv2.Externalcontact, *platformclientv2.APIResponse, error) {
externalContact, resp, err := p.externalContactsApi.PutExternalcontactsContact(externalContactId, *externalContact)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update external contact: %s", err)
}
return externalContact, resp, nil
}
package external_contacts
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_externalcontacts_contact.go contains all of the methods that perform the core logic for a resource.
In general a resource should have a approximately 5 methods in it:
1. A getAll.... function that the CX as Code exporter will use during the process of exporting Genesys Cloud.
2. A create.... function that the resource will use to create a Genesys Cloud object (e.g. genesycloud_externalcontacts_contacts)
3. A read.... function that looks up a single resource.
4. An update... function that updates a single resource.
5. A delete.... function that deletes a single resource.
Two things to note:
1. All code in these methods should be focused on getting data in and out of Terraform. All code that is used for interacting
with a Genesys API should be encapsulated into a proxy class contained within the package.
2. In general, to keep this file somewhat manageable, if you find yourself with a number of helper functions move them to a
utils function in the package. This will keep the code manageable and easy to work through.
*/
// getAllAuthExternalContacts retrieves all of the external contacts via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthExternalContacts(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
ep := getExternalContactsContactsProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
externalContacts, resp, err := ep.getAllExternalContacts(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get External Contacts error: %s", err), resp)
}
for _, externalContact := range *externalContacts {
log.Printf("Dealing with external contact id : %s", *externalContact.Id)
resources[*externalContact.Id] = &resourceExporter.ResourceMeta{Name: *externalContact.Id}
}
return resources, nil
}
// createExternalContact is used by the externalcontacts_contacts resource to create Genesyscloud external_contacts
func createExternalContact(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getExternalContactsContactsProxy(sdkConfig)
externalContact := getExternalContactFromResourceData(d)
contact, resp, err := ep.createExternalContact(ctx, &externalContact)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create external contact %s error: %s", *externalContact.Id, err), resp)
}
d.SetId(*contact.Id)
log.Printf("Created external contact %s", *contact.Id)
return readExternalContact(ctx, d, meta)
}
// readExternalContacts is used by the externalcontacts_contact resource to read an external contact from genesys cloud.
func readExternalContact(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getExternalContactsContactsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceExternalContact(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading contact %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
externalContact, resp, getErr := ep.getExternalContactById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read external contact %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read external contact %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "first_name", externalContact.FirstName)
resourcedata.SetNillableValue(d, "middle_name", externalContact.MiddleName)
resourcedata.SetNillableValue(d, "last_name", externalContact.LastName)
resourcedata.SetNillableValue(d, "salutation", externalContact.Salutation)
resourcedata.SetNillableValue(d, "title", externalContact.Title)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "work_phone", externalContact.WorkPhone, flattenPhoneNumber)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "cell_phone", externalContact.CellPhone, flattenPhoneNumber)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "home_phone", externalContact.HomePhone, flattenPhoneNumber)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "other_phone", externalContact.OtherPhone, flattenPhoneNumber)
resourcedata.SetNillableValue(d, "work_email", externalContact.WorkEmail)
resourcedata.SetNillableValue(d, "personal_email", externalContact.PersonalEmail)
resourcedata.SetNillableValue(d, "other_email", externalContact.OtherEmail)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "address", externalContact.Address, flattenSdkAddress)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "twitter_id", externalContact.TwitterId, flattenSdkTwitterId)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "line_id", externalContact.LineId, flattenSdkLineId)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "whatsapp_id", externalContact.WhatsAppId, flattenSdkWhatsAppId)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "facebook_id", externalContact.FacebookId, flattenSdkFacebookId)
resourcedata.SetNillableValue(d, "survey_opt_out", externalContact.SurveyOptOut)
resourcedata.SetNillableValue(d, "external_system_url", externalContact.ExternalSystemUrl)
log.Printf("Read external contact %s", d.Id())
return cc.CheckState(d)
})
}
// updateExternalContacts is used by the externalcontacts_contacts resource to update an external contact in Genesys Cloud
func updateExternalContact(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getExternalContactsContactsProxy(sdkConfig)
externalContact := getExternalContactFromResourceData(d)
_, resp, err := ep.updateExternalContact(ctx, d.Id(), &externalContact)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update external contact %s error: %s", *externalContact.Id, err), resp)
}
log.Printf("Updated external contact")
return readExternalContact(ctx, d, meta)
}
// deleteExternalContacts is used by the externalcontacts_contacts resource to delete an external contact from Genesys cloud.
func deleteExternalContact(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getExternalContactsContactsProxy(sdkConfig)
resp, err := ep.deleteExternalContactId(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete external contact %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := ep.getExternalContactById(ctx, d.Id())
if err == nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting external contact %s | error: %s", d.Id(), err), resp))
}
if util.IsStatus404(resp) {
// Success : External contact deleted
log.Printf("Deleted external contact %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("External contact %s still exists", d.Id()), resp))
})
}
package external_contacts
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
/*
resource_genesyscloud_externalcontacts_contacts_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the externalcontacts_contacts resource.
3. The datasource schema definitions for the externalcontacts_contacts datasource.
4. The resource exporter configuration for the externalcontacts_contacts exporter.
*/
const resourceName = "genesyscloud_externalcontacts_contact"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceExternalContactsContact())
l.RegisterResource(resourceName, ResourceExternalContact())
l.RegisterExporter(resourceName, ExternalContactExporter())
}
// ResourceExternalContact registers the genesyscloud_externalcontacts_contact resource with Terraform
func ResourceExternalContact() *schema.Resource {
phoneNumber := &schema.Resource{
Schema: map[string]*schema.Schema{
"display": {
Description: "Display string of the phone number.",
Type: schema.TypeString,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return hashFormattedPhoneNumber(old) == hashFormattedPhoneNumber(new)
},
Optional: true,
Computed: true,
},
"extension": {
Description: "Phone extension.",
Type: schema.TypeInt,
Optional: true,
},
"accepts_sms": {
Description: "If contact accept SMS.",
Type: schema.TypeBool,
Optional: true,
},
"e164": {
Description: "Phone number in e164 format.",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
"country_code": {
Description: "Phone number country code.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
address := &schema.Resource{
Schema: map[string]*schema.Schema{
"address1": {
Description: "Contact address 1.",
Type: schema.TypeString,
Optional: true,
},
"address2": {
Description: "Contact address 2.",
Type: schema.TypeString,
Optional: true,
},
"city": {
Description: "Contact address city.",
Type: schema.TypeString,
Optional: true,
},
"state": {
Description: "Contact address state.",
Type: schema.TypeString,
Optional: true,
},
"postal_code": {
Description: "Contact address postal code.",
Type: schema.TypeString,
Optional: true,
},
"country_code": {
Description: "Contact address country code.",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: gcloud.ValidateCountryCode,
},
},
}
twitterId := &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Description: "Contact twitter id.",
Type: schema.TypeString,
Optional: true,
},
"name": {
Description: "Contact twitter name.",
Type: schema.TypeString,
Optional: true,
},
"screen_name": {
Description: "Contact twitter screen name.",
Type: schema.TypeString,
Optional: true,
},
"profile_url": {
Description: "Contact twitter account url.",
Type: schema.TypeString,
Computed: true,
},
},
}
lineIds := &schema.Resource{
Schema: map[string]*schema.Schema{
"user_id": {
Description: "Contact line id.",
Type: schema.TypeString,
Optional: true,
},
},
}
lineId := &schema.Resource{
Schema: map[string]*schema.Schema{
"ids": {
Description: "Contact line id.",
Type: schema.TypeList,
Optional: true,
Elem: lineIds,
},
"display_name": {
Description: "Contact line display name.",
Type: schema.TypeString,
Optional: true,
},
},
}
whatsappId := &schema.Resource{
Schema: map[string]*schema.Schema{
"phone_number": {
Description: "Contact whatsapp phone number.",
Type: schema.TypeList,
Required: true,
Elem: phoneNumber,
},
"display_name": {
Description: "Contact whatsapp display name.",
Type: schema.TypeString,
Required: true,
},
},
}
facebookIds := &schema.Resource{
Schema: map[string]*schema.Schema{
"scoped_id": {
Description: "Contact facebook scoped id.",
Type: schema.TypeString,
Optional: true,
},
},
}
facebookId := &schema.Resource{
Schema: map[string]*schema.Schema{
"ids": {
Description: "Contact facebook scoped id.",
Type: schema.TypeList,
Optional: true,
Elem: facebookIds,
},
"display_name": {
Description: "Contact whatsapp display name.",
Type: schema.TypeString,
Optional: true,
},
},
}
return &schema.Resource{
Description: "Genesys Cloud External Contact",
CreateContext: provider.CreateWithPooledClient(createExternalContact),
ReadContext: provider.ReadWithPooledClient(readExternalContact),
UpdateContext: provider.UpdateWithPooledClient(updateExternalContact),
DeleteContext: provider.DeleteWithPooledClient(deleteExternalContact),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"first_name": {
Description: "The first name of the contact.",
Type: schema.TypeString,
Optional: true,
},
"middle_name": {
Description: "The middle name of the contact.",
Type: schema.TypeString,
Optional: true,
},
"last_name": {
Description: "The last name of the contact.",
Type: schema.TypeString,
Optional: true,
},
"salutation": {
Description: "The salutation of the contact.",
Type: schema.TypeString,
Optional: true,
},
"title": {
Description: "The title of the contact.",
Type: schema.TypeString,
Optional: true,
},
"work_phone": {
Description: "Contact work phone settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: phoneNumber,
},
"cell_phone": {
Description: "Contact call phone settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: phoneNumber,
},
"home_phone": {
Description: "Contact home phone settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: phoneNumber,
},
"other_phone": {
Description: "Contact other phone settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: phoneNumber,
},
"work_email": {
Description: "Contact work email.",
Type: schema.TypeString,
Optional: true,
},
"personal_email": {
Description: "Contact personal email.",
Type: schema.TypeString,
Optional: true,
},
"other_email": {
Description: "Contact other email.",
Type: schema.TypeString,
Optional: true,
},
"address": {
Description: "Contact address.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: address,
},
"twitter_id": {
Description: "Contact twitter account informations.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: twitterId,
},
"line_id": {
Description: "Contact line account informations.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: lineId,
},
"whatsapp_id": {
Description: "Contact whatsapp account informations.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: whatsappId,
},
"facebook_id": {
Description: "Contact facebook account informations.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: facebookId,
},
"survey_opt_out": {
Description: "Contact survey opt out preference.",
Type: schema.TypeBool,
Optional: true,
},
"external_system_url": {
Description: "Contact external system url.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
// ExternalContactExporter returns the resourceExporter object used to hold the genesyscloud_externalcontacts_contact exporter's config
func ExternalContactExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthExternalContacts),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"external_organization": {}, //Need to add this when we external orgs implemented
},
}
}
// DataSourceExternalContactsContact registers the genesyscloud_externalcontacts_contact data source
func DataSourceExternalContactsContact() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud external contacts. Select a contact by any string search.",
ReadContext: provider.ReadWithPooledClient(dataSourceExternalContactsContactRead),
Schema: map[string]*schema.Schema{
"search": {
Description: "The search string for the contact.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
package external_contacts
import (
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"github.com/nyaruka/phonenumbers"
)
/*
The resource_genesyscloud_externalcontacts_contacts_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
Note: Look for opportunities to minimize boilerplate code using functions and Generics
*/
// getExternalContactFromResourceData maps data from schema ResourceData object to a platformclientv2.Externalcontact
func getExternalContactFromResourceData(d *schema.ResourceData) platformclientv2.Externalcontact {
firstName := d.Get("first_name").(string)
middleName := d.Get("middle_name").(string)
lastName := d.Get("last_name").(string)
salutation := d.Get("salutation").(string)
title := d.Get("title").(string)
workEmail := d.Get("work_email").(string)
personalEmail := d.Get("personal_email").(string)
otherEmail := d.Get("other_email").(string)
surveyOptOut := d.Get("survey_opt_out").(bool)
externalSystemUrl := d.Get("external_system_url").(string)
return platformclientv2.Externalcontact{
FirstName: &firstName,
MiddleName: &middleName,
LastName: &lastName,
Salutation: &salutation,
Title: &title,
WorkPhone: buildSdkPhoneNumber(d, "work_phone"),
CellPhone: buildSdkPhoneNumber(d, "cell_phone"),
HomePhone: buildSdkPhoneNumber(d, "home_phone"),
OtherPhone: buildSdkPhoneNumber(d, "other_phone"),
WorkEmail: &workEmail,
PersonalEmail: &personalEmail,
OtherEmail: &otherEmail,
Address: buildSdkAddress(d, "address"),
TwitterId: buildSdkTwitterId(d, "twitter_id"),
LineId: buildSdkLineId(d, "line_id"),
WhatsAppId: buildSdkWhatsAppId(d, "whatsapp_id"),
FacebookId: buildSdkFacebookId(d, "facebook_id"),
SurveyOptOut: &surveyOptOut,
ExternalSystemUrl: &externalSystemUrl,
}
}
// buildPhonenumberFromData is a helper method to map phone data to the GenesysCloud platformclientv2.PhoneNumber
func buildPhonenumberFromData(phoneData []interface{}) *platformclientv2.Phonenumber {
phoneMap := phoneData[0].(map[string]interface{})
display := phoneMap["display"].(string)
extension := phoneMap["extension"].(int)
acceptSMS := phoneMap["accepts_sms"].(bool)
e164 := phoneMap["e164"].(string)
countryCode := phoneMap["country_code"].(string)
return &platformclientv2.Phonenumber{
Display: &display,
Extension: &extension,
AcceptsSMS: &acceptSMS,
E164: &e164,
CountryCode: &countryCode,
}
}
// buildSdkPhoneNumber is a helper method to build a Genesys Cloud SDK PhoneNumber
func buildSdkPhoneNumber(d *schema.ResourceData, key string) *platformclientv2.Phonenumber {
if d.Get(key) != nil {
phoneData := d.Get(key).([]interface{})
if len(phoneData) > 0 {
return buildPhonenumberFromData(phoneData)
}
}
return nil
}
// flattenPhoneNumber converts a platformclientv2.Phonenumber into a map and then into array for consumption by Terraform
func flattenPhoneNumber(phonenumber *platformclientv2.Phonenumber) []interface{} {
phonenumberInterface := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "display", phonenumber.Display)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "extension", phonenumber.Extension)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "accepts_sms", phonenumber.AcceptsSMS)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "e164", phonenumber.E164)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "country_code", phonenumber.CountryCode)
return []interface{}{phonenumberInterface}
}
// buildSdkAddress constructs a platformclientv2.Contactaddress structure
func buildSdkAddress(d *schema.ResourceData, key string) *platformclientv2.Contactaddress {
if d.Get(key) != nil {
addressData := d.Get(key).([]interface{})
if len(addressData) > 0 {
addressMap := addressData[0].(map[string]interface{})
address1 := addressMap["address1"].(string)
address2 := addressMap["address2"].(string)
city := addressMap["city"].(string)
state := addressMap["state"].(string)
postalcode := addressMap["postal_code"].(string)
countrycode := addressMap["country_code"].(string)
return &platformclientv2.Contactaddress{
Address1: &address1,
Address2: &address2,
City: &city,
State: &state,
PostalCode: &postalcode,
CountryCode: &countrycode,
}
}
}
return nil
}
// flattenflattenSdkAddress converts a *platformclientv2.Contactaddress into a map and then into array for consumption by Terraform
func flattenSdkAddress(address *platformclientv2.Contactaddress) []interface{} {
addressInterface := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(addressInterface, "address1", address.Address1)
resourcedata.SetMapValueIfNotNil(addressInterface, "address2", address.Address2)
resourcedata.SetMapValueIfNotNil(addressInterface, "city", address.City)
resourcedata.SetMapValueIfNotNil(addressInterface, "state", address.State)
resourcedata.SetMapValueIfNotNil(addressInterface, "postal_code", address.PostalCode)
resourcedata.SetMapValueIfNotNil(addressInterface, "country_code", address.CountryCode)
return []interface{}{addressInterface}
}
// buildSdkTwitterid maps data from a Terraform data object into a Genesys Cloud *platformclientv2.Twitterid
func buildSdkTwitterId(d *schema.ResourceData, key string) *platformclientv2.Twitterid {
if d.Get(key) != nil {
twitterData := d.Get(key).([]interface{})
if len(twitterData) > 0 {
twitterMap := twitterData[0].(map[string]interface{})
id := twitterMap["id"].(string)
name := twitterMap["name"].(string)
screenname := twitterMap["screen_name"].(string)
profileurl := twitterMap["profile_url"].(string)
return &platformclientv2.Twitterid{
Id: &id,
Name: &name,
ScreenName: &screenname,
ProfileUrl: &profileurl,
}
}
}
return nil
}
// flattenSdkTwitterId maps a Genesys Cloud platformclientv2.Twitterid into a []interface{}
func flattenSdkTwitterId(twitterId *platformclientv2.Twitterid) []interface{} {
twitterInterface := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(twitterInterface, "id", twitterId.Id)
resourcedata.SetMapValueIfNotNil(twitterInterface, "name", twitterId.Name)
if twitterId.ScreenName != nil {
url := "https://www.twitter.com/" + *twitterId.ScreenName
twitterInterface["screen_name"] = twitterId.ScreenName
twitterInterface["profile_url"] = &url
}
resourcedata.SetMapValueIfNotNil(twitterInterface, "profile_url", twitterId.ProfileUrl)
return []interface{}{twitterInterface}
}
// buildSdkLineId builds platformclientv2.Lineid struct from a Terramform resource data struct
func buildSdkLineId(d *schema.ResourceData, key string) *platformclientv2.Lineid {
if d.Get(key) != nil {
lineData := d.Get(key).([]interface{})
if len(lineData) > 0 {
lineMap := lineData[0].(map[string]interface{})
displayname := lineMap["display_name"].(string)
userId := lineMap["ids"].([]interface{})[0].(map[string]interface{})["user_id"].(string)
ids := []platformclientv2.Lineuserid{
{
UserId: &userId,
},
}
lineId := platformclientv2.Lineid{
DisplayName: &displayname,
Ids: &ids,
}
return &lineId
}
}
return nil
}
// flattenSdkLineId maps platformclientv2.Lineid to a []interace{}
func flattenSdkLineId(lineId *platformclientv2.Lineid) []interface{} {
lineInterface := make(map[string]interface{})
flattenUserid := flattenSdkLineUserId(lineId.Ids)
lineInterface["display_name"] = *lineId.DisplayName
lineInterface["ids"] = &flattenUserid
return []interface{}{lineInterface}
}
// flattenSdkLineUserId maps an []platformclientv2.Lineuserid to a []interface{}
func flattenSdkLineUserId(lineUserdid *[]platformclientv2.Lineuserid) []interface{} {
lineUseridInterface := make(map[string]interface{})
if (*lineUserdid)[0].UserId != nil {
lineUseridInterface["user_id"] = (*lineUserdid)[0].UserId
}
return []interface{}{lineUseridInterface}
}
// buildSdkWhatsAppId maps a Terraform schema.ResourceData to a Genesys Cloud platformclientv2.Whatsappid
func buildSdkWhatsAppId(d *schema.ResourceData, key string) *platformclientv2.Whatsappid {
if d.Get(key) != nil {
whatsappData := d.Get(key).([]interface{})
if len(whatsappData) > 0 {
whatsappMap := whatsappData[0].(map[string]interface{})
displayName := whatsappMap["display_name"].(string)
return &platformclientv2.Whatsappid{
DisplayName: &displayName,
PhoneNumber: buildPhonenumberFromData(whatsappMap["phone_number"].([]interface{})),
}
}
}
return nil
}
// flattenSdkWhatsAppId maps a Genesys Cloud platformclientv2.Whatsappid to a []interface{}
func flattenSdkWhatsAppId(whatsappId *platformclientv2.Whatsappid) []interface{} {
whatsappInterface := make(map[string]interface{})
flattenPhonenumber := flattenPhoneNumber(whatsappId.PhoneNumber)
whatsappInterface["display_name"] = *whatsappId.DisplayName
whatsappInterface["phone_number"] = &flattenPhonenumber
return []interface{}{whatsappInterface}
}
// buildSdkFacebookId maps a Terraform schema.ResourceData struct to a Genesys Cloud platformclientv2.Facebookid
func buildSdkFacebookId(d *schema.ResourceData, key string) *platformclientv2.Facebookid {
if d.Get(key) != nil {
facebookData := d.Get(key).([]interface{})
if len(facebookData) > 0 {
facebookMap := facebookData[0].(map[string]interface{})
displayname := facebookMap["display_name"].(string)
scopedId := facebookMap["ids"].([]interface{})[0].(map[string]interface{})["scoped_id"].(string)
facebookIds := []platformclientv2.Facebookscopedid{
{
ScopedId: &scopedId,
},
}
facebookId := platformclientv2.Facebookid{
DisplayName: &displayname,
Ids: &facebookIds,
}
return &facebookId
}
}
return nil
}
// flattenSdkFacebookId maps a Genesys Cloud platformclientv2.Facebookid object to a []interface{}
func flattenSdkFacebookId(facebookid *platformclientv2.Facebookid) []interface{} {
whatsappInterface := make(map[string]interface{})
flattenScopedid := flattenSdkFacebookScopedId(facebookid.Ids)
whatsappInterface["display_name"] = *facebookid.DisplayName
whatsappInterface["ids"] = &flattenScopedid
return []interface{}{whatsappInterface}
}
// flattenSdkFacebookScopedId maps a Genesys Cloud platformclientv2.Facebookscopedid struct ot a []interface{}
func flattenSdkFacebookScopedId(facebookScopedid *[]platformclientv2.Facebookscopedid) []interface{} {
facebookScopedidInterface := make(map[string]interface{})
if (*facebookScopedid)[0].ScopedId != nil {
facebookScopedidInterface["scoped_id"] = (*facebookScopedid)[0].ScopedId
}
return []interface{}{facebookScopedidInterface}
}
// formatPhoneNumber formats a given string to E164 format and hashes it for comparison
func hashFormattedPhoneNumber(val string) int {
formattedNumber := ""
number, err := phonenumbers.Parse(val, "US")
if err == nil {
formattedNumber = phonenumbers.Format(number, phonenumbers.E164)
}
return schema.HashString(formattedNumber)
}
func GenerateBasicExternalContactResource(resourceID string, title string) string {
return fmt.Sprintf(`resource "genesyscloud_externalcontacts_contact" "%s" {
title = "%s"
}
`, resourceID, title)
}
package flow_loglevel
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_flow_loglevel_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *flowLogLevelProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createFlowLogLevelFunc func(ctx context.Context, p *flowLogLevelProxy, flowId string, flowLogLevelRequest *platformclientv2.Flowloglevelrequest) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error)
type getAllFlowLogLevelsFunc func(ctx context.Context, p *flowLogLevelProxy) (*[]platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error)
type getFlowLogLevelByIdFunc func(ctx context.Context, p *flowLogLevelProxy, flowId string) (flowLogLevel *platformclientv2.Flowsettingsresponse, apiResponse *platformclientv2.APIResponse, err error)
type updateFlowLogLevelFunc func(ctx context.Context, p *flowLogLevelProxy, flowId string, flowLogLevel *platformclientv2.Flowloglevelrequest) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error)
type deleteFlowLogLevelFunc func(ctx context.Context, p *flowLogLevelProxy, flowId string) (*platformclientv2.APIResponse, error)
// flowLogLevelProxy contains all the methods that call genesys cloud APIs.
type flowLogLevelProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createFlowLogLevelAttr createFlowLogLevelFunc
getFlowLogLevelByIdAttr getFlowLogLevelByIdFunc
getAllFlowLogLevelsAttr getAllFlowLogLevelsFunc
updateFlowLogLevelAttr updateFlowLogLevelFunc
deleteFlowLogLevelByIdAttr deleteFlowLogLevelFunc
}
// newFlowLogLevelProxy initializes the Flow Log Level proxy with all the data needed to communicate with Genesys Cloud
func newFlowLogLevelProxy(clientConfig *platformclientv2.Configuration) *flowLogLevelProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &flowLogLevelProxy{
clientConfig: clientConfig,
architectApi: api,
createFlowLogLevelAttr: createFlowLogLevelFn,
getAllFlowLogLevelsAttr: getAllFlowLogLevelsFn,
getFlowLogLevelByIdAttr: getFlowLogLevelByIdFn,
updateFlowLogLevelAttr: updateFlowLogLevelFn,
deleteFlowLogLevelByIdAttr: deleteFlowLogLevelsFn,
}
}
// getFlowLogLevelProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getFlowLogLevelProxy(clientConfig *platformclientv2.Configuration) *flowLogLevelProxy {
if internalProxy == nil {
internalProxy = newFlowLogLevelProxy(clientConfig)
}
return internalProxy
}
// getAllFlowLogLevels retrieves all Genesys Cloud Flow Log Levels
func (p *flowLogLevelProxy) getAllFlowLogLevels(ctx context.Context) (*[]platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
return p.getAllFlowLogLevelsAttr(ctx, p)
}
// createFlowLogLevel creates a Genesys Cloud Flow Log Level
func (p *flowLogLevelProxy) createFlowLogLevel(ctx context.Context, flowLogLevelId string, flowLogLevelRequest *platformclientv2.Flowloglevelrequest) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
return p.createFlowLogLevelAttr(ctx, p, flowLogLevelId, flowLogLevelRequest)
}
// getFlowLogLevelById returns a single Genesys Cloud Flow Log Level by Id
func (p *flowLogLevelProxy) getFlowLogLevelById(ctx context.Context, flowId string) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
return p.getFlowLogLevelByIdAttr(ctx, p, flowId)
}
// updateFlowLogLevel updates a Genesys Cloud Flow Log Level
func (p *flowLogLevelProxy) updateFlowLogLevel(ctx context.Context, flowId string, flowLogLevelRequest *platformclientv2.Flowloglevelrequest) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
return p.updateFlowLogLevelAttr(ctx, p, flowId, flowLogLevelRequest)
}
// DeleteFlowLogLevel deletes a Genesys Cloud Flow Log Level by Id
func (p *flowLogLevelProxy) deleteFlowLogLevelById(ctx context.Context, flowId string) (*platformclientv2.APIResponse, error) {
return p.deleteFlowLogLevelByIdAttr(ctx, p, flowId)
}
// createFlowLogLevelFn is an implementation function for creating a Genesys Cloud Flow Log Level
func createFlowLogLevelFn(ctx context.Context, p *flowLogLevelProxy, flowId string, flowLogLevelRequest *platformclientv2.Flowloglevelrequest) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
flowLogLevel, apiResponse, err := p.architectApi.PostFlowInstancesSettingsLoglevels(flowId, *flowLogLevelRequest, nil)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to create flow log level: %s", err)
}
return flowLogLevel, apiResponse, nil
}
// getFlowLogLevelByIdFn is an implementation of the function to get a Genesys Cloud Flow Log Level by Id
func getFlowLogLevelByIdFn(ctx context.Context, p *flowLogLevelProxy, flowId string) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
flowLogLevel, apiResponse, err := p.architectApi.GetFlowInstancesSettingsLoglevels(flowId, nil)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to retrieve flow log level by id %s: %s", flowId, err)
}
return flowLogLevel, apiResponse, nil
}
// getAllFlowLogLevelsFn is the implementation for retrieving all flow log levels in Genesys Cloud
func getAllFlowLogLevelsFn(ctx context.Context, p *flowLogLevelProxy) (*[]platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
const pageSize = 100
var totalFlowLogLevels []platformclientv2.Flowsettingsresponse
flowSettingsResponse, apiResponse, err := p.architectApi.GetFlowsInstancesSettingsLoglevels(nil, 1, pageSize)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to get page of flows: %v", err)
}
if flowSettingsResponse.Entities == nil || len(*flowSettingsResponse.Entities) == 0 {
return &totalFlowLogLevels, apiResponse, nil
}
totalFlowLogLevels = append(totalFlowLogLevels, *flowSettingsResponse.Entities...)
for pageNum := 2; pageNum <= *flowSettingsResponse.PageCount; pageNum++ {
flowSettingsResponse, apiResponse, err := p.architectApi.GetFlowsInstancesSettingsLoglevels(nil, pageNum, pageSize)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to get page %d of flow log levels: %v", pageNum, err)
}
if flowSettingsResponse.Entities == nil || len(*flowSettingsResponse.Entities) == 0 {
return &totalFlowLogLevels, apiResponse, nil
}
totalFlowLogLevels = append(totalFlowLogLevels, *flowSettingsResponse.Entities...)
}
return &totalFlowLogLevels, apiResponse, nil
}
// updateFlowLogLevelFn is an implementation of the function to update a Genesys Cloud flow log level
func updateFlowLogLevelFn(ctx context.Context, p *flowLogLevelProxy, flowLogLevelId string, flowLogLevelRequest *platformclientv2.Flowloglevelrequest) (*platformclientv2.Flowsettingsresponse, *platformclientv2.APIResponse, error) {
flowSettingsResponse, apiResponse, err := p.architectApi.PutFlowInstancesSettingsLoglevels(flowLogLevelId, *flowLogLevelRequest, nil)
if err != nil {
return nil, apiResponse, fmt.Errorf("Failed to update flow log level: %s", err)
}
return flowSettingsResponse, apiResponse, nil
}
// deleteFlowLogLevelsFn is an implementation function for deleting a Genesys Cloud Flow Log Level
func deleteFlowLogLevelsFn(ctx context.Context, p *flowLogLevelProxy, flowLogLevelId string) (*platformclientv2.APIResponse, error) {
apiResponse, err := p.architectApi.DeleteFlowInstancesSettingsLoglevels(flowLogLevelId)
if err != nil {
return apiResponse, fmt.Errorf("Failed to delete flow log level: %s", err)
}
return apiResponse, nil
}
package flow_loglevel
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_flow_loglevel.go contains all of the methods that perform the core logic for a resource.
In general a resource should have a approximately 5 methods in it:
1. A getAll.... function that the CX as Code exporter will use during the process of exporting Genesys Cloud.
2. A create.... function that the resource will use to create a Genesys Cloud object (e.g. genesycloud_flow_logLevel)
3. A read.... function that looks up a single resource.
4. An update... function that updates a single resource.
5. A delete.... function that deletes a single resource.
Two things to note:
1. All code in these methods should be focused on getting data in and out of Terraform. All code that is used for interacting
with a Genesys API should be encapsulated into a proxy class contained within the package.
2. In general, to keep this file somewhat manageable, if you find yourself with a number of helper functions move them to a
utils function in the package. This will keep the code manageable and easy to work through.
*/
// getAllFlowLogLevels retrieves all of the flow log levels via Terraform in the Genesys Cloud and is used for the exporter
func getAllFlowLogLevels(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
ep := getFlowLogLevelProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
flowLogLevels, apiResponse, err := ep.getAllFlowLogLevels(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get flow log levels: %v", err), apiResponse)
}
for _, flowLogLevel := range *flowLogLevels {
resources[*flowLogLevel.Id] = &resourceExporter.ResourceMeta{Name: *flowLogLevel.Id}
}
return resources, nil
}
// createFlowLogLevel is used by the flow_loglevel resource to create Genesyscloud flow_loglevel
func createFlowLogLevel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getFlowLogLevelProxy(sdkConfig)
flowId := d.Get("flow_id").(string)
log.Printf("Creating flow log level for flow %s", flowId)
flowLogLevelRequest := platformclientv2.Flowloglevelrequest{
LogLevelCharacteristics: getFlowLogLevelFromResourceData(d),
}
flowLogLevel, apiResponse, err := ep.createFlowLogLevel(ctx, flowId, &flowLogLevelRequest)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create flow log level: %s %s", err, d.Id()), apiResponse)
}
log.Printf("Sucessfully created flow log level for flow: %s flowLogLevelId: %s", flowId, *flowLogLevel.Id)
d.SetId(*flowLogLevel.Id)
return readFlowLogLevel(ctx, d, meta)
}
// readFlowLogLevels is used by the flow_loglevel resource to read a flow log level from genesys cloud.
func readFlowLogLevel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getFlowLogLevelProxy(sdkConfig)
flowId := d.Get("flow_id").(string)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceFlowLoglevel(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading readFlowLogLevel with flowId %s", flowId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
flowSettingsResponse, apiResponse, err := ep.getFlowLogLevelById(ctx, flowId)
if err != nil {
if util.IsStatus404ByInt(apiResponse.StatusCode) {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow log level %s | error: %s", flowId, err), apiResponse))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow log level %s | error: %s", flowId, err), apiResponse))
}
flowLogLevel := flowSettingsResponse.LogLevelCharacteristics
resourcedata.SetNillableValue(d, "flow_log_level", flowLogLevel.Level)
log.Printf("Read flow log level %s", flowId)
return cc.CheckState(d)
})
}
// updateFlowLogLevels is used by the flow_loglevel resource to update an flow log level in Genesys Cloud
func updateFlowLogLevel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getFlowLogLevelProxy(sdkConfig)
flowId := d.Get("flow_id").(string)
log.Printf("Updating flow log level for flow %s", flowId)
flowLogLevelRequest := platformclientv2.Flowloglevelrequest{
LogLevelCharacteristics: getFlowLogLevelFromResourceData(d),
}
updatedFlow, apiResponse, err := ep.updateFlowLogLevel(ctx, flowId, &flowLogLevelRequest)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update flow log level: %s %s", flowId, d.Id()), apiResponse)
}
log.Printf("Sucessfully updated flow log level for flow: %s flowLogLevelId: %s", flowId, *updatedFlow.Id)
return readFlowLogLevel(ctx, d, meta)
}
// deleteFlowLogLevels is used by the flow_loglevel resource to delete an flow log level from Genesys cloud.
func deleteFlowLogLevel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ep := getFlowLogLevelProxy(sdkConfig)
flowId := d.Get("flow_id").(string)
log.Printf("Deleting flow log level for flow %s", flowId)
apiResponse, err := ep.deleteFlowLogLevelById(ctx, flowId)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete flow log level %s: %s", flowId, d.Id()), apiResponse)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, apiResponse, err := ep.getFlowLogLevelById(ctx, flowId)
if err == nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting flow log level %s %s | error: %s", flowId, d.Id(), err), apiResponse))
}
if util.IsStatus404ByInt(apiResponse.StatusCode) {
log.Printf("Deleted flow log level %s", flowId)
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("flow log level %s still exists", flowId), apiResponse))
})
}
// getFlowLogLevelFromResourceData maps data from schema ResourceData object to a platformclientv2.Flowloglevel
func getFlowLogLevelFromResourceData(d *schema.ResourceData) *platformclientv2.Flowloglevel {
return &platformclientv2.Flowloglevel{
Level: platformclientv2.String(d.Get("flow_log_level").(string)),
}
}
package flow_loglevel
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_flow_loglevel_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the flow_loglevel resource.
3. The datasource schema definitions for the flow_loglevel datasource.
4. The resource exporter configuration for the flow_loglevel exporter.
*/
const resourceName = "genesyscloud_flow_loglevel"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceFlowLoglevel())
}
// FlowMilestoneExporter returns the resourceExporter object used to hold the genesyscloud_flow_milestone exporter's config
func FlowLogLevelExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllFlowLogLevels),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
// ResourceFlowLoglevel registers the genesyscloud_flow_loglevel resource with Terraform
func ResourceFlowLoglevel() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud flow log level`,
CreateContext: provider.CreateWithPooledClient(createFlowLogLevel),
ReadContext: provider.ReadWithPooledClient(readFlowLogLevel),
UpdateContext: provider.UpdateWithPooledClient(updateFlowLogLevel),
DeleteContext: provider.DeleteWithPooledClient(deleteFlowLogLevel),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"flow_id": {
Description: "The flowId for this characteristics set",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"flow_log_level": {
Description: "The logLevel for this characteristics set",
Type: schema.TypeString,
Required: true,
},
},
}
}
package flow_milestone
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_flow_milestone.go contains the data source implementation
for the resource.
*/
// dataSourceFlowMilestoneRead retrieves by name the id in question
func dataSourceFlowMilestoneRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newFlowMilestoneProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
flowMilestoneId, retryable, resp, err := proxy.getFlowMilestoneIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching flow milestone %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No flow milestone found with name %s", name), resp))
}
d.SetId(flowMilestoneId)
return nil
})
}
package flow_milestone
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_flow_milestone_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *flowMilestoneProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createFlowMilestoneFunc func(ctx context.Context, p *flowMilestoneProxy, flowMilestone *platformclientv2.Flowmilestone) (*platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error)
type getAllFlowMilestoneFunc func(ctx context.Context, p *flowMilestoneProxy) (*[]platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error)
type getFlowMilestoneIdByNameFunc func(ctx context.Context, p *flowMilestoneProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getFlowMilestoneByIdFunc func(ctx context.Context, p *flowMilestoneProxy, id string) (flowMilestone *platformclientv2.Flowmilestone, response *platformclientv2.APIResponse, err error)
type updateFlowMilestoneFunc func(ctx context.Context, p *flowMilestoneProxy, id string, flowMilestone *platformclientv2.Flowmilestone) (*platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error)
type deleteFlowMilestoneFunc func(ctx context.Context, p *flowMilestoneProxy, id string) (response *platformclientv2.APIResponse, err error)
// flowMilestoneProxy contains all of the methods that call genesys cloud APIs.
type flowMilestoneProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createFlowMilestoneAttr createFlowMilestoneFunc
getAllFlowMilestoneAttr getAllFlowMilestoneFunc
getFlowMilestoneIdByNameAttr getFlowMilestoneIdByNameFunc
getFlowMilestoneByIdAttr getFlowMilestoneByIdFunc
updateFlowMilestoneAttr updateFlowMilestoneFunc
deleteFlowMilestoneAttr deleteFlowMilestoneFunc
}
// newFlowMilestoneProxy initializes the flow milestone proxy with all of the data needed to communicate with Genesys Cloud
func newFlowMilestoneProxy(clientConfig *platformclientv2.Configuration) *flowMilestoneProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &flowMilestoneProxy{
clientConfig: clientConfig,
architectApi: api,
createFlowMilestoneAttr: createFlowMilestoneFn,
getAllFlowMilestoneAttr: getAllFlowMilestoneFn,
getFlowMilestoneIdByNameAttr: getFlowMilestoneIdByNameFn,
getFlowMilestoneByIdAttr: getFlowMilestoneByIdFn,
updateFlowMilestoneAttr: updateFlowMilestoneFn,
deleteFlowMilestoneAttr: deleteFlowMilestoneFn,
}
}
// getFlowMilestoneProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getFlowMilestoneProxy(clientConfig *platformclientv2.Configuration) *flowMilestoneProxy {
if internalProxy == nil {
internalProxy = newFlowMilestoneProxy(clientConfig)
}
return internalProxy
}
// createFlowMilestone creates a Genesys Cloud flow milestone
func (p *flowMilestoneProxy) createFlowMilestone(ctx context.Context, flowMilestone *platformclientv2.Flowmilestone) (*platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error) {
return p.createFlowMilestoneAttr(ctx, p, flowMilestone)
}
// getFlowMilestone retrieves all Genesys Cloud flow milestone
func (p *flowMilestoneProxy) getAllFlowMilestone(ctx context.Context) (*[]platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error) {
return p.getAllFlowMilestoneAttr(ctx, p)
}
// getFlowMilestoneIdByName returns a single Genesys Cloud flow milestone by a name
func (p *flowMilestoneProxy) getFlowMilestoneIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getFlowMilestoneIdByNameAttr(ctx, p, name)
}
// getFlowMilestoneById returns a single Genesys Cloud flow milestone by Id
func (p *flowMilestoneProxy) getFlowMilestoneById(ctx context.Context, id string) (flowMilestone *platformclientv2.Flowmilestone, response *platformclientv2.APIResponse, err error) {
return p.getFlowMilestoneByIdAttr(ctx, p, id)
}
// updateFlowMilestone updates a Genesys Cloud flow milestone
func (p *flowMilestoneProxy) updateFlowMilestone(ctx context.Context, id string, flowMilestone *platformclientv2.Flowmilestone) (*platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error) {
return p.updateFlowMilestoneAttr(ctx, p, id, flowMilestone)
}
// deleteFlowMilestone deletes a Genesys Cloud flow milestone by Id
func (p *flowMilestoneProxy) deleteFlowMilestone(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteFlowMilestoneAttr(ctx, p, id)
}
// createFlowMilestoneFn is an implementation function for creating a Genesys Cloud flow milestone
func createFlowMilestoneFn(ctx context.Context, p *flowMilestoneProxy, flowMilestone *platformclientv2.Flowmilestone) (*platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error) {
flowMilestone, resp, err := p.architectApi.PostFlowsMilestones(*flowMilestone)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create flow milestone: %s ", err)
}
return flowMilestone, resp, nil
}
// getAllFlowMilestoneFn is the implementation for retrieving all flow milestone in Genesys Cloud
func getAllFlowMilestoneFn(ctx context.Context, p *flowMilestoneProxy) (*[]platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error) {
var allFlowMilestones []platformclientv2.Flowmilestone
const pageSize = 100
flowMilestones, resp, err := p.architectApi.GetFlowsMilestones(1, pageSize, "", "", nil, "", "", "", nil)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get flow milestone: %v", err)
}
if flowMilestones.Entities == nil || len(*flowMilestones.Entities) == 0 {
return &allFlowMilestones, resp, nil
}
for _, flowMilestone := range *flowMilestones.Entities {
allFlowMilestones = append(allFlowMilestones, flowMilestone)
}
for pageNum := 2; pageNum <= *flowMilestones.PageCount; pageNum++ {
flowMilestones, resp, err := p.architectApi.GetFlowsMilestones(pageNum, pageSize, "", "", nil, "", "", "", nil)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get flow milestone: %v", err)
}
if flowMilestones.Entities == nil || len(*flowMilestones.Entities) == 0 {
break
}
for _, flowMilestone := range *flowMilestones.Entities {
allFlowMilestones = append(allFlowMilestones, flowMilestone)
}
}
return &allFlowMilestones, resp, nil
}
// getFlowMilestoneIdByNameFn is an implementation of the function to get a Genesys Cloud flow milestone by name
func getFlowMilestoneIdByNameFn(ctx context.Context, p *flowMilestoneProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
flowMilestones, resp, err := p.architectApi.GetFlowsMilestones(1, 100, "", "", nil, name, "", "", nil)
if err != nil {
return "", false, resp, err
}
if flowMilestones.Entities == nil || len(*flowMilestones.Entities) == 0 {
return "", true, resp, fmt.Errorf("No flow milestone found with name %s", name)
}
for _, flowMilestoneSdk := range *flowMilestones.Entities {
if *flowMilestoneSdk.Name == name {
log.Printf("Retrieved the flow milestone id %s by name %s", *flowMilestoneSdk.Id, name)
return *flowMilestoneSdk.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find flow milestone with name %s", name)
}
// getFlowMilestoneByIdFn is an implementation of the function to get a Genesys Cloud flow milestone by Id
func getFlowMilestoneByIdFn(ctx context.Context, p *flowMilestoneProxy, id string) (flowMilestone *platformclientv2.Flowmilestone, response *platformclientv2.APIResponse, err error) {
flowMilestone, resp, err := p.architectApi.GetFlowsMilestone(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve flow milestone by id %s: %s", id, err)
}
return flowMilestone, resp, nil
}
// updateFlowMilestoneFn is an implementation of the function to update a Genesys Cloud flow milestone
func updateFlowMilestoneFn(ctx context.Context, p *flowMilestoneProxy, id string, flowMilestone *platformclientv2.Flowmilestone) (*platformclientv2.Flowmilestone, *platformclientv2.APIResponse, error) {
flowMilestone, resp, err := p.architectApi.PutFlowsMilestone(id, *flowMilestone)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update flow milestone: %s", err)
}
return flowMilestone, resp, nil
}
// deleteFlowMilestoneFn is an implementation function for deleting a Genesys Cloud flow milestone
func deleteFlowMilestoneFn(ctx context.Context, p *flowMilestoneProxy, id string) (response *platformclientv2.APIResponse, err error) {
_, resp, err := p.architectApi.DeleteFlowsMilestone(id)
if err != nil {
return resp, fmt.Errorf("Failed to delete flow milestone: %s", err)
}
return resp, nil
}
package flow_milestone
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_flow_milestone.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthFlowMilestone retrieves all of the flow milestone via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthFlowMilestones(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := newFlowMilestoneProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
flowMilestones, resp, err := proxy.getAllFlowMilestone(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get flow milestone error: %s", err), resp)
}
for _, flowMilestone := range *flowMilestones {
resources[*flowMilestone.Id] = &resourceExporter.ResourceMeta{Name: *flowMilestone.Name}
}
return resources, nil
}
// createFlowMilestone is used by the flow_milestone resource to create Genesys cloud flow milestone
func createFlowMilestone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getFlowMilestoneProxy(sdkConfig)
flowMilestone := getFlowMilestoneFromResourceData(d)
log.Printf("Creating flow milestone %s", *flowMilestone.Name)
flowMilestoneSdk, resp, err := proxy.createFlowMilestone(ctx, &flowMilestone)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create flow milestone %s error: %s", *flowMilestone.Name, err), resp)
}
d.SetId(*flowMilestoneSdk.Id)
log.Printf("Created flow milestone %s", *flowMilestoneSdk.Id)
return readFlowMilestone(ctx, d, meta)
}
// readFlowMilestone is used by the flow_milestone resource to read a flow milestone from genesys cloud
func readFlowMilestone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getFlowMilestoneProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceFlowMilestone(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading flow milestone %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
flowMilestone, resp, getErr := proxy.getFlowMilestoneById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow milestone %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow milestone %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", flowMilestone.Name)
resourcedata.SetNillableReferenceWritableDivision(d, "division_id", flowMilestone.Division)
resourcedata.SetNillableValue(d, "description", flowMilestone.Description)
log.Printf("Read flow milestone %s %s", d.Id(), *flowMilestone.Name)
return cc.CheckState(d)
})
}
// updateFlowMilestone is used by the flow_milestone resource to update an flow milestone in Genesys Cloud
func updateFlowMilestone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getFlowMilestoneProxy(sdkConfig)
flowMilestone := getFlowMilestoneFromResourceData(d)
log.Printf("Updating flow milestone %s", *flowMilestone.Name)
flowMilestoneSdk, resp, err := proxy.updateFlowMilestone(ctx, d.Id(), &flowMilestone)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update flow milestone %s error: %s", *flowMilestone.Name, err), resp)
}
log.Printf("Updated flow milestone %s", *flowMilestoneSdk.Id)
return readFlowMilestone(ctx, d, meta)
}
// deleteFlowMilestone is used by the flow_milestone resource to delete a flow milestone from Genesys cloud
func deleteFlowMilestone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getFlowMilestoneProxy(sdkConfig)
resp, err := proxy.deleteFlowMilestone(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete flow milestone %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getFlowMilestoneById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted flow milestone %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting flow milestone %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("flow milestone %s still exists", d.Id()), resp))
})
}
package flow_milestone
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_flow_milestone_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the flow_milestone resource.
3. The datasource schema definitions for the flow_milestone datasource.
4. The resource exporter configuration for the flow_milestone exporter.
*/
const resourceName = "genesyscloud_flow_milestone"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceFlowMilestone())
regInstance.RegisterDataSource(resourceName, DataSourceFlowMilestone())
regInstance.RegisterExporter(resourceName, FlowMilestoneExporter())
}
// ResourceFlowMilestone registers the genesyscloud_flow_milestone resource with Terraform
func ResourceFlowMilestone() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud flow milestone`,
CreateContext: provider.CreateWithPooledClient(createFlowMilestone),
ReadContext: provider.ReadWithPooledClient(readFlowMilestone),
UpdateContext: provider.UpdateWithPooledClient(updateFlowMilestone),
DeleteContext: provider.DeleteWithPooledClient(deleteFlowMilestone),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The flow milestone name.",
Required: true,
Type: schema.TypeString,
},
"division_id": {
Description: "The division to which this entity belongs.",
Optional: true,
Computed: true,
Type: schema.TypeString,
},
"description": {
Description: "The flow milestone description.",
Optional: true,
Type: schema.TypeString,
},
},
}
}
// FlowMilestoneExporter returns the resourceExporter object used to hold the genesyscloud_flow_milestone exporter's config
func FlowMilestoneExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthFlowMilestones),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
},
}
}
// DataSourceFlowMilestone registers the genesyscloud_flow_milestone data source
func DataSourceFlowMilestone() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud flow milestone data source. Select a flow milestone by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceFlowMilestoneRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `flow milestone name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package flow_milestone
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_flow_outcome_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
// getFlowMilestoneFromResourceData maps data from schema ResourceData object to a platformclientv2.Flowmilestone
func getFlowMilestoneFromResourceData(d *schema.ResourceData) platformclientv2.Flowmilestone {
divisionId := d.Get("division_id").(string)
description := d.Get("description").(string)
milestone := platformclientv2.Flowmilestone{
Name: platformclientv2.String(d.Get("name").(string)),
}
if divisionId != "" {
milestone.Division = &platformclientv2.Writabledivision{Id: &divisionId}
}
if description != "" {
milestone.Description = &description
}
return milestone
}
package flow_outcome
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_flow_outcome.go contains the data source implementation
for the resource.
*/
// dataSourceFlowOutcomeRead retrieves by name the id in question
func dataSourceFlowOutcomeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newFlowOutcomeProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
flowOutcomeId, retryable, resp, err := proxy.getFlowOutcomeIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching flow outcome %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No flow outcome found with name %s", name), resp))
}
d.SetId(flowOutcomeId)
return nil
})
}
package flow_outcome
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_flow_outcome_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *flowOutcomeProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createFlowOutcomeFunc func(ctx context.Context, p *flowOutcomeProxy, flowOutcome *platformclientv2.Flowoutcome) (*platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error)
type getAllFlowOutcomeFunc func(ctx context.Context, p *flowOutcomeProxy) (*[]platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error)
type getFlowOutcomeIdByNameFunc func(ctx context.Context, p *flowOutcomeProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getFlowOutcomeByIdFunc func(ctx context.Context, p *flowOutcomeProxy, id string) (flowOutcome *platformclientv2.Flowoutcome, response *platformclientv2.APIResponse, err error)
type updateFlowOutcomeFunc func(ctx context.Context, p *flowOutcomeProxy, id string, flowOutcome *platformclientv2.Flowoutcome) (*platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error)
// flowOutcomeProxy contains all of the methods that call genesys cloud APIs.
type flowOutcomeProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
createFlowOutcomeAttr createFlowOutcomeFunc
getAllFlowOutcomeAttr getAllFlowOutcomeFunc
getFlowOutcomeIdByNameAttr getFlowOutcomeIdByNameFunc
getFlowOutcomeByIdAttr getFlowOutcomeByIdFunc
updateFlowOutcomeAttr updateFlowOutcomeFunc
}
// newFlowOutcomeProxy initializes the flow outcome proxy with all of the data needed to communicate with Genesys Cloud
func newFlowOutcomeProxy(clientConfig *platformclientv2.Configuration) *flowOutcomeProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &flowOutcomeProxy{
clientConfig: clientConfig,
architectApi: api,
createFlowOutcomeAttr: createFlowOutcomeFn,
getAllFlowOutcomeAttr: getAllFlowOutcomeFn,
getFlowOutcomeIdByNameAttr: getFlowOutcomeIdByNameFn,
getFlowOutcomeByIdAttr: getFlowOutcomeByIdFn,
updateFlowOutcomeAttr: updateFlowOutcomeFn,
}
}
// getFlowOutcomeProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getFlowOutcomeProxy(clientConfig *platformclientv2.Configuration) *flowOutcomeProxy {
if internalProxy == nil {
internalProxy = newFlowOutcomeProxy(clientConfig)
}
return internalProxy
}
// createFlowOutcome creates a Genesys Cloud flow outcome
func (p *flowOutcomeProxy) createFlowOutcome(ctx context.Context, flowOutcome *platformclientv2.Flowoutcome) (*platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error) {
return p.createFlowOutcomeAttr(ctx, p, flowOutcome)
}
// getFlowOutcome retrieves all Genesys Cloud flow outcome
func (p *flowOutcomeProxy) getAllFlowOutcome(ctx context.Context) (*[]platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error) {
return p.getAllFlowOutcomeAttr(ctx, p)
}
// getFlowOutcomeIdByName returns a single Genesys Cloud flow outcome by a name
func (p *flowOutcomeProxy) getFlowOutcomeIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getFlowOutcomeIdByNameAttr(ctx, p, name)
}
// getFlowOutcomeById returns a single Genesys Cloud flow outcome by Id
func (p *flowOutcomeProxy) getFlowOutcomeById(ctx context.Context, id string) (flowOutcome *platformclientv2.Flowoutcome, response *platformclientv2.APIResponse, err error) {
return p.getFlowOutcomeByIdAttr(ctx, p, id)
}
// updateFlowOutcome updates a Genesys Cloud flow outcome
func (p *flowOutcomeProxy) updateFlowOutcome(ctx context.Context, id string, flowOutcome *platformclientv2.Flowoutcome) (*platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error) {
return p.updateFlowOutcomeAttr(ctx, p, id, flowOutcome)
}
// createFlowOutcomeFn is an implementation function for creating a Genesys Cloud flow outcome
func createFlowOutcomeFn(ctx context.Context, p *flowOutcomeProxy, flowOutcome *platformclientv2.Flowoutcome) (*platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error) {
flowOutcome, resp, err := p.architectApi.PostFlowsOutcomes(*flowOutcome)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create flow outcome: %s", err)
}
return flowOutcome, resp, nil
}
// getAllFlowOutcomeFn is the implementation for retrieving all flow outcome in Genesys Cloud
func getAllFlowOutcomeFn(ctx context.Context, p *flowOutcomeProxy) (*[]platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error) {
var allFlowOutcomes []platformclientv2.Flowoutcome
const pageSize = 100
flowOutcomes, resp, err := p.architectApi.GetFlowsOutcomes(1, pageSize, "", "", nil, "", "", "", nil)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get flow outcome: %v", err)
}
if flowOutcomes.Entities == nil || len(*flowOutcomes.Entities) == 0 {
return &allFlowOutcomes, resp, nil
}
for _, flowOutcome := range *flowOutcomes.Entities {
allFlowOutcomes = append(allFlowOutcomes, flowOutcome)
}
for pageNum := 2; pageNum <= *flowOutcomes.PageCount; pageNum++ {
flowOutcomes, resp, err := p.architectApi.GetFlowsOutcomes(pageNum, pageSize, "", "", nil, "", "", "", nil)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get flow outcome: %v", err)
}
if flowOutcomes.Entities == nil || len(*flowOutcomes.Entities) == 0 {
break
}
for _, flowOutcome := range *flowOutcomes.Entities {
allFlowOutcomes = append(allFlowOutcomes, flowOutcome)
}
}
return &allFlowOutcomes, resp, nil
}
// getFlowOutcomeIdByNameFn is an implementation of the function to get a Genesys Cloud flow outcome by name
func getFlowOutcomeIdByNameFn(ctx context.Context, p *flowOutcomeProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
flowOutcomes, resp, err := p.architectApi.GetFlowsOutcomes(1, 100, "", "", nil, name, "", "", nil)
if err != nil {
return "", false, resp, err
}
if flowOutcomes.Entities == nil || len(*flowOutcomes.Entities) == 0 {
return "", true, resp, fmt.Errorf("No flow outcome found with name %s", name)
}
for _, flowOutcomeSdk := range *flowOutcomes.Entities {
if *flowOutcomeSdk.Name == name {
log.Printf("Retrieved the flow outcome id %s by name %s", *flowOutcomeSdk.Id, name)
return *flowOutcomeSdk.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find flow outcome with name %s", name)
}
// getFlowOutcomeByIdFn is an implementation of the function to get a Genesys Cloud flow outcome by Id
func getFlowOutcomeByIdFn(ctx context.Context, p *flowOutcomeProxy, id string) (flowOutcome *platformclientv2.Flowoutcome, response *platformclientv2.APIResponse, err error) {
flowOutcome, resp, err := p.architectApi.GetFlowsOutcome(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve flow outcome by id %s: %s", id, err)
}
return flowOutcome, resp, nil
}
// updateFlowOutcomeFn is an implementation of the function to update a Genesys Cloud flow outcome
func updateFlowOutcomeFn(ctx context.Context, p *flowOutcomeProxy, id string, flowOutcome *platformclientv2.Flowoutcome) (*platformclientv2.Flowoutcome, *platformclientv2.APIResponse, error) {
_, resp, err := p.architectApi.PutFlowsOutcome(id, *flowOutcome)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update flow outcome: %s", err)
}
return flowOutcome, resp, nil
}
package flow_outcome
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_flow_outcome.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthFlowOutcome retrieves all of the flow outcome via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthFlowOutcomes(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := newFlowOutcomeProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
flowOutcomes, resp, err := proxy.getAllFlowOutcome(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get flow outcomes error: %s", err), resp)
}
for _, flowOutcome := range *flowOutcomes {
resources[*flowOutcome.Id] = &resourceExporter.ResourceMeta{Name: *flowOutcome.Name}
}
return resources, nil
}
// createFlowOutcome is used by the flow_outcome resource to create Genesys cloud flow outcome
func createFlowOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getFlowOutcomeProxy(sdkConfig)
flowOutcome := getFlowOutcomeFromResourceData(d)
log.Printf("Creating flow outcome %s", *flowOutcome.Name)
outcome, resp, err := proxy.createFlowOutcome(ctx, &flowOutcome)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create flow outcome %s error: %s", *flowOutcome.Name, err), resp)
}
d.SetId(*outcome.Id)
log.Printf("Created flow outcome %s", *outcome.Id)
return readFlowOutcome(ctx, d, meta)
}
// readFlowOutcome is used by the flow_outcome resource to read an flow outcome from genesys cloud
func readFlowOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getFlowOutcomeProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceFlowOutcome(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading flow outcome %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
flowOutcome, resp, getErr := proxy.getFlowOutcomeById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow outcome %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow outcome %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", flowOutcome.Name)
resourcedata.SetNillableReferenceWritableDivision(d, "division_id", flowOutcome.Division)
// The api is adding a description with a space to outcomes without a description with is causing a mismatch with the state
// We need to check if the description is not " " before reading it
if flowOutcome.Description != nil && *flowOutcome.Description != " " {
_ = d.Set("description", *flowOutcome.Description)
} else {
_ = d.Set("description", nil)
}
log.Printf("Read flow outcome %s %s", d.Id(), *flowOutcome.Name)
return cc.CheckState(d)
})
}
// updateFlowOutcome is used by the flow_outcome resource to update an flow outcome in Genesys Cloud
func updateFlowOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getFlowOutcomeProxy(sdkConfig)
flowOutcome := getFlowOutcomeFromResourceData(d)
log.Printf("Updating flow outcome %s", *flowOutcome.Name)
_, resp, err := proxy.updateFlowOutcome(ctx, d.Id(), &flowOutcome)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update flow outcome %s error: %s", *flowOutcome.Name, err), resp)
}
log.Printf("Updated flow outcome %s", d.Id())
return readFlowOutcome(ctx, d, meta)
}
// deleteFlowOutcome is used by the flow_outcome resource to delete an flow outcome from Genesys cloud
func deleteFlowOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}
package flow_outcome
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_flow_outcome_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the flow_outcome resource.
3. The datasource schema definitions for the flow_outcome datasource.
4. The resource exporter configuration for the flow_outcome exporter.
*/
const resourceName = "genesyscloud_flow_outcome"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceFlowOutcome())
regInstance.RegisterDataSource(resourceName, DataSourceFlowOutcome())
regInstance.RegisterExporter(resourceName, FlowOutcomeExporter())
}
// ResourceFlowOutcome registers the genesyscloud_flow_outcome resource with Terraform
func ResourceFlowOutcome() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud flow outcome`,
CreateContext: provider.CreateWithPooledClient(createFlowOutcome),
ReadContext: provider.ReadWithPooledClient(readFlowOutcome),
UpdateContext: provider.UpdateWithPooledClient(updateFlowOutcome),
DeleteContext: provider.DeleteWithPooledClient(deleteFlowOutcome),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The flow outcome name.",
Required: true,
Type: schema.TypeString,
},
"division_id": {
Description: "The division to which this entity belongs.",
Optional: true,
Computed: true,
Type: schema.TypeString,
},
"description": {
Description: "This is a description for the flow outcome.",
Optional: true,
Type: schema.TypeString,
},
},
}
}
// FlowOutcomeExporter returns the resourceExporter object used to hold the genesyscloud_flow_outcome exporter's config
func FlowOutcomeExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthFlowOutcomes),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
},
}
}
// DataSourceFlowOutcome registers the genesyscloud_flow_outcome data source
func DataSourceFlowOutcome() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud flow outcome data source. Select a flow outcome by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceFlowOutcomeRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `flow outcome name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package flow_outcome
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_flow_outcome_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
// getFlowOutcomeFromResourceData maps data from schema ResourceData object to a platformclientv2.Flowoutcome
func getFlowOutcomeFromResourceData(d *schema.ResourceData) platformclientv2.Flowoutcome {
divisionId := d.Get("division_id").(string)
description := d.Get("description").(string)
outcome := platformclientv2.Flowoutcome{
Name: platformclientv2.String(d.Get("name").(string)),
}
if divisionId != "" {
outcome.Division = &platformclientv2.Writabledivision{Id: &divisionId}
}
if description != "" {
outcome.Description = &description
}
return outcome
}
package group
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
gp := getGroupProxy(sdkConfig)
nameStr := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
groups, resp, getErr := gp.getGroupsByName(ctx, nameStr)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting group %s | error: %s", nameStr, getErr), resp))
}
if *groups.Total > 1 {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Multiple groups found with name %s ", nameStr), resp))
}
if *groups.Total == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No groups found with name %s ", nameStr), resp))
}
// Select first group in the list
group := (*groups.Results)[0]
d.SetId(*group.Id)
return nil
})
}
package group
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
)
var internalProxy *groupProxy
type createGroupFunc func(ctx context.Context, p *groupProxy, group *platformclientv2.Groupcreate) (*platformclientv2.Group, *platformclientv2.APIResponse, error)
type getAllGroupFunc func(ctx context.Context, p *groupProxy) (*[]platformclientv2.Group, *platformclientv2.APIResponse, error)
type updateGroupFunc func(ctx context.Context, p *groupProxy, id string, group *platformclientv2.Groupupdate) (*platformclientv2.Group, *platformclientv2.APIResponse, error)
type getGroupByIdFunc func(ctx context.Context, p *groupProxy, id string) (*platformclientv2.Group, *platformclientv2.APIResponse, error)
type addGroupMembersFunc func(ctx context.Context, p *groupProxy, id string, members *platformclientv2.Groupmembersupdate) (*interface{}, *platformclientv2.APIResponse, error)
type deleteGroupMembersFunc func(ctx context.Context, p *groupProxy, id string, members string) (*interface{}, *platformclientv2.APIResponse, error)
type getGroupMembersFunc func(ctx context.Context, p *groupProxy, id string) (*[]string, *platformclientv2.APIResponse, error)
type getGroupByNameFunc func(ctx context.Context, p *groupProxy, name string) (*platformclientv2.Groupssearchresponse, *platformclientv2.APIResponse, error)
type deleteGroupFunc func(ctx context.Context, p *groupProxy, id string) (*platformclientv2.APIResponse, error)
type groupProxy struct {
clientConfig *platformclientv2.Configuration
groupsApi *platformclientv2.GroupsApi
createGroupAttr createGroupFunc
getAllGroupAttr getAllGroupFunc
updateGroupAttr updateGroupFunc
deleteGroupAttr deleteGroupFunc
getGroupByNameAttr getGroupByNameFunc
getGroupByIdAttr getGroupByIdFunc
addGroupMembersAttr addGroupMembersFunc
deleteGroupMembersAttr deleteGroupMembersFunc
getGroupMembersAttr getGroupMembersFunc
groupCache rc.CacheInterface[platformclientv2.Group]
}
func newGroupProxy(clientConfig *platformclientv2.Configuration) *groupProxy {
api := platformclientv2.NewGroupsApiWithConfig(clientConfig)
groupCache := rc.NewResourceCache[platformclientv2.Group]()
return &groupProxy{
clientConfig: clientConfig,
groupsApi: api,
createGroupAttr: createGroupFn,
getAllGroupAttr: getAllGroupFn,
updateGroupAttr: updateGroupFn,
deleteGroupAttr: deleteGroupFn,
getGroupByNameAttr: getGroupByNameFn,
getGroupByIdAttr: getGroupByIdFn,
addGroupMembersAttr: addGroupMembersFn,
deleteGroupMembersAttr: deleteGroupMembersFn,
getGroupMembersAttr: getGroupMembersFn,
groupCache: groupCache,
}
}
func getGroupProxy(clientConfig *platformclientv2.Configuration) *groupProxy {
if internalProxy == nil {
internalProxy = newGroupProxy(clientConfig)
}
return internalProxy
}
func (p *groupProxy) createGroup(ctx context.Context, group *platformclientv2.Groupcreate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) {
return p.createGroupAttr(ctx, p, group)
}
func (p *groupProxy) updateGroup(ctx context.Context, id string, group *platformclientv2.Groupupdate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) {
return p.updateGroupAttr(ctx, p, id, group)
}
func (p *groupProxy) deleteGroup(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return p.deleteGroupAttr(ctx, p, id)
}
func (p *groupProxy) getAllGroups(ctx context.Context) (*[]platformclientv2.Group, *platformclientv2.APIResponse, error) {
return p.getAllGroupAttr(ctx, p)
}
func (p *groupProxy) getGroupById(ctx context.Context, id string) (*platformclientv2.Group, *platformclientv2.APIResponse, error) {
return p.getGroupByIdAttr(ctx, p, id)
}
func (p *groupProxy) addGroupMembers(ctx context.Context, id string, members *platformclientv2.Groupmembersupdate) (*interface{}, *platformclientv2.APIResponse, error) {
return p.addGroupMembersAttr(ctx, p, id, members)
}
func (p *groupProxy) deleteGroupMembers(ctx context.Context, id string, members string) (*interface{}, *platformclientv2.APIResponse, error) {
return p.deleteGroupMembersAttr(ctx, p, id, members)
}
func (p *groupProxy) getGroupMembers(ctx context.Context, id string) (*[]string, *platformclientv2.APIResponse, error) {
return p.getGroupMembersAttr(ctx, p, id)
}
func (p *groupProxy) getGroupsByName(ctx context.Context, name string) (*platformclientv2.Groupssearchresponse, *platformclientv2.APIResponse, error) {
return p.getGroupByNameAttr(ctx, p, name)
}
func createGroupFn(_ context.Context, p *groupProxy, group *platformclientv2.Groupcreate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) {
return p.groupsApi.PostGroups(*group)
}
func updateGroupFn(_ context.Context, p *groupProxy, id string, group *platformclientv2.Groupupdate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) {
return p.groupsApi.PutGroup(id, *group)
}
func deleteGroupFn(_ context.Context, p *groupProxy, id string) (*platformclientv2.APIResponse, error) {
return p.groupsApi.DeleteGroup(id)
}
func getGroupByIdFn(_ context.Context, p *groupProxy, id string) (*platformclientv2.Group, *platformclientv2.APIResponse, error) {
group := rc.GetCacheItem(p.groupCache, id)
if group != nil {
return group, nil, nil
}
return p.groupsApi.GetGroup(id)
}
func addGroupMembersFn(_ context.Context, p *groupProxy, id string, members *platformclientv2.Groupmembersupdate) (*interface{}, *platformclientv2.APIResponse, error) {
return p.groupsApi.PostGroupMembers(id, *members)
}
func deleteGroupMembersFn(_ context.Context, p *groupProxy, id string, members string) (*interface{}, *platformclientv2.APIResponse, error) {
return p.groupsApi.DeleteGroupMembers(id, members)
}
func getGroupMembersFn(_ context.Context, p *groupProxy, id string) (*[]string, *platformclientv2.APIResponse, error) {
members, response, err := p.groupsApi.GetGroupIndividuals(id)
if err != nil {
return nil, response, err
}
var existingMembers []string
if members.Entities != nil {
for _, member := range *members.Entities {
existingMembers = append(existingMembers, *member.Id)
}
}
return &existingMembers, nil, nil
}
func getGroupByNameFn(_ context.Context, p *groupProxy, name string) (*platformclientv2.Groupssearchresponse, *platformclientv2.APIResponse, error) {
exactSearchType := "EXACT"
nameField := "name"
nameStr := name
searchCriteria := platformclientv2.Groupsearchcriteria{
VarType: &exactSearchType,
Value: &nameStr,
Fields: &[]string{nameField},
}
groups, resp, getErr := p.groupsApi.PostGroupsSearch(platformclientv2.Groupsearchrequest{
Query: &[]platformclientv2.Groupsearchcriteria{searchCriteria},
})
return groups, resp, getErr
}
func getAllGroupFn(_ context.Context, p *groupProxy) (*[]platformclientv2.Group, *platformclientv2.APIResponse, error) {
var allGroups []platformclientv2.Group
const pageSize = 100
groups, resp, getErr := p.groupsApi.GetGroups(pageSize, 1, nil, nil, "")
if getErr != nil {
return nil, resp, fmt.Errorf("failed to get first page of groups: %v", getErr)
}
allGroups = append(allGroups, *groups.Entities...)
for pageNum := 2; pageNum <= *groups.PageCount; pageNum++ {
groups, resp, getErr := p.groupsApi.GetGroups(pageSize, pageNum, nil, nil, "")
if getErr != nil {
return nil, resp, fmt.Errorf("failed to get page of groups: %v", getErr)
}
allGroups = append(allGroups, *groups.Entities...)
}
for _, group := range allGroups {
rc.SetCache(p.groupCache, *group.Id, group)
}
return &allGroups, nil, nil
}
package group
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/chunks"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func GetAllGroups(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
groupProxy := getGroupProxy(clientConfig)
groups, resp, err := groupProxy.getAllGroups(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to retrieve all groups: %s", err), resp)
}
for _, group := range *groups {
resources[*group.Id] = &resourceExporter.ResourceMeta{Name: *group.Name}
}
return resources, nil
}
func createGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
groupType := d.Get("type").(string)
visibility := d.Get("visibility").(string)
rulesVisible := d.Get("rules_visible").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
gp := getGroupProxy(sdkConfig)
addresses, err := buildSdkGroupAddresses(d)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error Building SDK group addresses"), err)
}
createGroup := &platformclientv2.Groupcreate{
Name: &name,
VarType: &groupType,
Visibility: &visibility,
RulesVisible: &rulesVisible,
Addresses: addresses,
OwnerIds: lists.BuildSdkStringListFromInterfaceArray(d, "owner_ids"),
}
log.Printf("Creating group %s", name)
group, resp, err := gp.createGroup(ctx, createGroup)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create group %s: %s", name, err), resp)
}
d.SetId(*group.Id)
// Description can only be set in a PUT. This is a bug with the API and has been reported
if description != "" {
diagErr := updateGroup(ctx, d, meta)
if diagErr != nil {
return diagErr
}
}
diagErr := updateGroupMembers(ctx, d, sdkConfig)
if diagErr != nil {
return diagErr
}
log.Printf("Created group %s %s", name, *group.Id)
return readGroup(ctx, d, meta)
}
func readGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceGroup(), constants.DefaultConsistencyChecks, resourceName)
gp := getGroupProxy(sdkConfig)
log.Printf("Reading group %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
group, resp, getErr := gp.getGroupById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read group %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read group %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", group.Name)
resourcedata.SetNillableValue(d, "type", group.VarType)
resourcedata.SetNillableValue(d, "visibility", group.Visibility)
resourcedata.SetNillableValue(d, "rules_visible", group.RulesVisible)
resourcedata.SetNillableValue(d, "description", group.Description)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "owner_ids", group.Owners, flattenGroupOwners)
if group.Addresses != nil {
_ = d.Set("addresses", flattenGroupAddresses(d, group.Addresses))
} else {
_ = d.Set("addresses", nil)
}
members, err := readGroupMembers(ctx, d.Id(), sdkConfig)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
_ = d.Set("member_ids", members)
log.Printf("Read group %s %s", d.Id(), *group.Name)
return cc.CheckState(d)
})
}
func updateGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
visibility := d.Get("visibility").(string)
rulesVisible := d.Get("rules_visible").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
gp := getGroupProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current group version
group, resp, getErr := gp.getGroupById(ctx, d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read group %s: %s", d.Id(), getErr), resp)
}
addresses, err := buildSdkGroupAddresses(d)
if err != nil {
return resp, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error while trying to buildSdkGroupAddresses for group id: %s", d.Id()), err)
}
log.Printf("Updating group %s", name)
updateGroup := &platformclientv2.Groupupdate{
Version: group.Version,
Name: &name,
Description: &description,
Visibility: &visibility,
RulesVisible: &rulesVisible,
Addresses: addresses,
OwnerIds: lists.BuildSdkStringListFromInterfaceArray(d, "owner_ids"),
}
_, resp, putErr := gp.updateGroup(ctx, d.Id(), updateGroup)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update group %s: %s", d.Id(), putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
diagErr = updateGroupMembers(ctx, d, sdkConfig)
if diagErr != nil {
return diagErr
}
log.Printf("Updated group %s", name)
return readGroup(ctx, d, meta)
}
func deleteGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
gp := getGroupProxy(sdkConfig)
util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Directory occasionally returns version errors on deletes if an object was updated at the same time.
log.Printf("Deleting group %s", name)
resp, err := gp.deleteGroup(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete group %s: %s", name, err), resp)
}
return nil, nil
})
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
group, resp, err := gp.getGroupById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Group %s deleted", name)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting group %s | error: %s", d.Id(), err), resp))
}
if group.State != nil && *group.State == "deleted" {
log.Printf("Group %s deleted", name)
return nil
}
/*
This extra delete call is being added here because of DEVTOOLING-485. Basically we are in a transition
state with the groups API. We have two services BEVY and Directory that are managing groups. Bevy is dual
writing to directory. However, Directory always returns a 200 on the delete and then fails asynchronously.
As a result the delete sometimes does not occur and then we just keep picking it up as it has.
After talking with Joe Fruland, the team lead for directory we are putting this extra DELETE in here
to keep trying to delete in case of this situation.
*/
resp, err = gp.deleteGroup(ctx, d.Id())
if err != nil {
log.Printf("Error while trying to delete group %s inside of the delete retry. Correlation id of failed call %s",
name, resp.CorrelationID)
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Group %s still exists", d.Id()), resp))
})
}
func updateGroupMembers(ctx context.Context, d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
gp := getGroupProxy(sdkConfig)
if d.HasChange("member_ids") {
if membersConfig := d.Get("member_ids"); membersConfig != nil {
configMemberIds := *lists.SetToStringList(membersConfig.(*schema.Set))
existingMemberIds, err := getGroupMemberIds(ctx, d, sdkConfig)
if err != nil {
return err
}
maxMembersPerRequest := 50
membersToRemoveList := lists.SliceDifference(existingMemberIds, configMemberIds)
chunkedMemberIdsDelete := chunks.ChunkBy(membersToRemoveList, maxMembersPerRequest)
chunkProcessor := func(membersToRemove []string) diag.Diagnostics {
if len(membersToRemove) > 0 {
if diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
_, resp, err := gp.deleteGroupMembers(ctx, d.Id(), strings.Join(membersToRemove, ","))
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to remove members from group %s: %s", d.Id(), err), resp)
}
return resp, nil
}); diagErr != nil {
return diagErr
}
}
return nil
}
if err := chunks.ProcessChunks(chunkedMemberIdsDelete, chunkProcessor); err != nil {
return err
}
membersToAdd := lists.SliceDifference(configMemberIds, existingMemberIds)
if len(membersToAdd) < 1 {
return nil
}
chunkedMemberIds := lists.ChunkStringSlice(membersToAdd, maxMembersPerRequest)
for _, chunk := range chunkedMemberIds {
if err := addGroupMembers(ctx, d, chunk, sdkConfig); err != nil {
return err
}
}
}
}
return nil
}
func readGroupMembers(ctx context.Context, groupID string, sdkConfig *platformclientv2.Configuration) (*schema.Set, diag.Diagnostics) {
gp := getGroupProxy(sdkConfig)
members, resp, err := gp.getGroupMembers(ctx, groupID)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read members for group %s: %s", groupID, err), resp)
}
interfaceList := make([]interface{}, len(*members))
for i, v := range *members {
interfaceList[i] = v
}
return schema.NewSet(schema.HashString, interfaceList), nil
}
func getGroupMemberIds(ctx context.Context, d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) ([]string, diag.Diagnostics) {
gp := getGroupProxy(sdkConfig)
members, resp, err := gp.getGroupMembers(ctx, d.Id())
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Unable to retrieve members for group %s. %s", d.Id(), err), resp)
}
return *members, nil
}
func addGroupMembers(ctx context.Context, d *schema.ResourceData, membersToAdd []string, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
gp := getGroupProxy(sdkConfig)
if diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Need the current group version to add members
groupInfo, resp, getErr := gp.getGroupById(ctx, d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read group %s: %s", d.Id(), getErr), resp)
}
groupMemberUpdate := &platformclientv2.Groupmembersupdate{
MemberIds: &membersToAdd,
Version: groupInfo.Version,
}
_, resp, postErr := gp.addGroupMembers(ctx, d.Id(), groupMemberUpdate)
if postErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to add group members %s: %s", d.Id(), postErr), resp)
}
return resp, nil
}); diagErr != nil {
return diagErr
}
return nil
}
package group
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/validators"
)
const resourceName = "genesyscloud_group"
var (
groupPhoneType = "PHONE"
groupAddressResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"number": {
Description: "Phone number for this contact type. Must be in an E.164 number format.",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validators.ValidatePhoneNumber,
},
"extension": {
Description: "Phone extension.",
Type: schema.TypeString,
Optional: true,
},
"type": {
Description: "Contact type of the address. (GROUPRING | GROUPPHONE)",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"GROUPRING", "GROUPPHONE"}, false),
},
},
}
)
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceGroup())
regInstance.RegisterDataSource(resourceName, DataSourceGroup())
regInstance.RegisterExporter(resourceName, GroupExporter())
}
func GroupExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(GetAllGroups),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"owner_ids": {RefType: "genesyscloud_user"},
"member_ids": {RefType: "genesyscloud_user"},
},
CustomValidateExports: map[string][]string{
"E164": {"addresses.number"},
},
}
}
func ResourceGroup() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Directory Group",
CreateContext: provider.CreateWithPooledClient(createGroup),
ReadContext: provider.ReadWithPooledClient(readGroup),
UpdateContext: provider.UpdateWithPooledClient(updateGroup),
DeleteContext: provider.DeleteWithPooledClient(deleteGroup),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Group name.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "Group description.",
Type: schema.TypeString,
Optional: true,
},
"type": {
Description: "Group type (official | social). This cannot be modified. Changing type attribute will cause the existing genesys_group object to dropped and recreated with a new ID.",
Type: schema.TypeString,
Optional: true,
Default: "official",
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"official", "social"}, false),
},
"visibility": {
Description: "Who can view this group (public | owners | members).",
Type: schema.TypeString,
Optional: true,
Default: "public",
ValidateFunc: validation.StringInSlice([]string{"public", "owners", "members"}, false),
},
"rules_visible": {
Description: "Are membership rules visible to the person requesting to view the group.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"addresses": {
Description: "Contact numbers for this group.",
Type: schema.TypeList,
Optional: true,
Elem: groupAddressResource,
},
"owner_ids": {
Description: "IDs of owners of the group.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
},
"member_ids": {
Description: "IDs of members assigned to the group. If not set, this resource will not manage group members.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
func DataSourceGroup() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Groups. Select a group by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceGroupRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Group name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package group
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
// 'number' and 'extension' conflict with eachother. However, one must be set.
// This function validates that the user has satisfied these conditions
func validateAddressesMap(m map[string]interface{}) error {
number, _ := m["number"].(string)
extension, _ := m["extension"].(string)
if (number != "" && extension != "") ||
(number == "" && extension == "") {
return fmt.Errorf("Either 'number' or 'extension' must be set inside addresses, but both cannot be set.")
}
return nil
}
func flattenGroupAddresses(d *schema.ResourceData, addresses *[]platformclientv2.Groupcontact) []interface{} {
addressSlice := make([]interface{}, 0)
for _, address := range *addresses {
if address.MediaType != nil {
if *address.MediaType == groupPhoneType {
phoneNumber := make(map[string]interface{})
// Strip off any parentheses from phone numbers
if address.Address != nil {
phoneNumber["number"] = strings.Trim(*address.Address, "()")
}
resourcedata.SetMapValueIfNotNil(phoneNumber, "extension", address.Extension)
resourcedata.SetMapValueIfNotNil(phoneNumber, "type", address.VarType)
// Sometimes the number or extension is only returned in Display
if address.Address == nil &&
address.Extension == nil &&
address.Display != nil {
setExtensionOrNumberBasedOnDisplay(d, phoneNumber, &address)
}
addressSlice = append(addressSlice, phoneNumber)
} else {
log.Printf("Unknown address media type %s", *address.MediaType)
}
}
}
return addressSlice
}
/**
* The api can sometimes return only the display which holds the value
* that was stored in either `address` or `extension`
* This function establishes which field was set in the schema data (`extension` or `address`)
* and then sets that field in the map to the value that came back in `display`
*/
func setExtensionOrNumberBasedOnDisplay(d *schema.ResourceData, addressMap map[string]interface{}, address *platformclientv2.Groupcontact) {
display := strings.Trim(*address.Display, "()")
schemaAddresses := d.Get("addresses").([]interface{})
for _, a := range schemaAddresses {
currentAddress, ok := a.(map[string]interface{})
if !ok {
continue
}
addressType, _ := currentAddress["type"].(string)
if addressType != *address.VarType {
continue
}
if ext, _ := currentAddress["extension"].(string); ext != "" {
addressMap["extension"] = display
} else if number, _ := currentAddress["number"].(string); number != "" {
addressMap["number"] = display
}
}
}
func flattenGroupOwners(owners *[]platformclientv2.User) []interface{} {
interfaceList := make([]interface{}, len(*owners))
for i, v := range *owners {
interfaceList[i] = *v.Id
}
return interfaceList
}
func buildSdkGroupAddresses(d *schema.ResourceData) (*[]platformclientv2.Groupcontact, error) {
if addressSlice, ok := d.Get("addresses").([]interface{}); ok && len(addressSlice) > 0 {
sdkContacts := make([]platformclientv2.Groupcontact, len(addressSlice))
for i, configPhone := range addressSlice {
phoneMap := configPhone.(map[string]interface{})
phoneType := phoneMap["type"].(string)
contact := platformclientv2.Groupcontact{
VarType: &phoneType,
MediaType: &groupPhoneType, // Only option is PHONE
}
if err := validateAddressesMap(phoneMap); err != nil {
return nil, err
}
if phoneNum, ok := phoneMap["number"].(string); ok && phoneNum != "" {
contact.Address = &phoneNum
}
if phoneExt := phoneMap["extension"].(string); ok && phoneExt != "" {
contact.Extension = &phoneExt
}
sdkContacts[i] = contact
}
return &sdkContacts, nil
}
return nil, nil
}
func GenerateBasicGroupResource(resourceID string, name string, nestedBlocks ...string) string {
return GenerateGroupResource(resourceID, name, util.NullValue, util.NullValue, util.NullValue, util.TrueValue, nestedBlocks...)
}
func GenerateGroupResource(
resourceID string,
name string,
desc string,
groupType string,
visibility string,
rulesVisible string,
nestedBlocks ...string) string {
return fmt.Sprintf(`resource "genesyscloud_group" "%s" {
name = "%s"
description = %s
type = %s
visibility = %s
rules_visible = %s
%s
}
`, resourceID, name, desc, groupType, visibility, rulesVisible, strings.Join(nestedBlocks, "\n"))
}
func generateGroupAddress(number string, phoneType string, extension string) string {
return fmt.Sprintf(`addresses {
number = %s
type = "%s"
extension = %s
}
`, number, phoneType, extension)
}
func GenerateGroupOwners(userIDs ...string) string {
return fmt.Sprintf(`owner_ids = [%s]
`, strings.Join(userIDs, ","))
}
func generateGroupMembers(userIDs ...string) string {
return fmt.Sprintf(`member_ids = [%s]
`, strings.Join(userIDs, ","))
}
package group_roles
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *groupRolesProxy
type getGroupRolesByIdFunc func(ctx context.Context, p *groupRolesProxy, roleId string) (*[]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error)
type updateGroupRolesFunc func(ctx context.Context, p *groupRolesProxy, roleId string, rolesConfig *schema.Set, subjectType string) (*platformclientv2.APIResponse, error)
type groupRolesProxy struct {
clientConfig *platformclientv2.Configuration
authorizationApi *platformclientv2.AuthorizationApi
getGroupRolesByIdAttr getGroupRolesByIdFunc
updateGroupRolesAttr updateGroupRolesFunc
}
func newGroupRolesProxy(clientConfig *platformclientv2.Configuration) *groupRolesProxy {
api := platformclientv2.NewAuthorizationApiWithConfig(clientConfig)
return &groupRolesProxy{
clientConfig: clientConfig,
authorizationApi: api,
getGroupRolesByIdAttr: getGroupRolesByIdFn,
updateGroupRolesAttr: updateGroupRolesFn,
}
}
func getGroupRolesProxy(clientConfig *platformclientv2.Configuration) *groupRolesProxy {
if internalProxy == nil {
internalProxy = newGroupRolesProxy(clientConfig)
}
return internalProxy
}
func (p *groupRolesProxy) getGroupRolesById(ctx context.Context, roleId string) (*[]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error) {
return p.getGroupRolesByIdAttr(ctx, p, roleId)
}
func (p *groupRolesProxy) updateGroupRoles(ctx context.Context, roleID string, rolesConfig *schema.Set, subjectType string) (*platformclientv2.APIResponse, error) {
return p.updateGroupRolesAttr(ctx, p, roleID, rolesConfig, subjectType)
}
func getGroupRolesByIdFn(_ context.Context, p *groupRolesProxy, roleId string) (*[]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error) {
var grants []platformclientv2.Authzgrant
subject, resp, err := p.authorizationApi.GetAuthorizationSubject(roleId, true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get current grants for subject %s: %s", roleId, err)
}
if subject != nil && subject.Grants != nil {
for _, grant := range *subject.Grants {
if grant.SubjectId != nil && *grant.SubjectId == roleId {
grants = append(grants, grant)
}
}
}
if err != nil {
return nil, resp, err
}
return &grants, resp, nil
}
func updateGroupRolesFn(_ context.Context, p *groupRolesProxy, roleId string, rolesConfig *schema.Set, subjectType string) (*platformclientv2.APIResponse, error) {
// Get existing roles/divisions
subject, resp, err := p.authorizationApi.GetAuthorizationSubject(roleId, true)
grants, resp, err := getAssignedGrants(*subject.Id, p)
existingGrants, configGrants, _ := getExistingAndConfigGrants(grants, rolesConfig)
if err != nil {
return resp, fmt.Errorf("failed to get current grants for subject %s: %s", roleId, err)
}
if subject != nil && subject.Grants != nil {
for _, grant := range *subject.Grants {
if grant.SubjectId != nil && *grant.SubjectId == roleId {
grants = append(grants, grant)
}
}
}
grantsToRemove, grantsToAdd := getGrantsToAddAndRemove(existingGrants, configGrants)
if len(grantsToRemove) > 0 {
// It's possible for a role or division to be removed before this update is processed,
// and the bulk remove API returns failure if any roles/divisions no longer exist.
// Work around by removing all grants individually and ignore 404s.
sdkGrantsToRemove := roleDivPairsToGrants(grantsToRemove)
for _, grant := range *sdkGrantsToRemove.Grants {
resp, err := p.authorizationApi.DeleteAuthorizationSubjectDivisionRole(roleId, *grant.DivisionId, *grant.RoleId)
if err != nil {
if resp == nil || resp.StatusCode != 404 {
return resp, fmt.Errorf("failed to remove role grants for subject %s: %s", roleId, err)
}
}
}
}
if len(grantsToAdd) > 0 {
// In some cases new roles or divisions have not yet been added to the auth service cache causing 404s that should be retried.
diagErr := util.RetryWhen(util.IsStatus404, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
resp, err := p.authorizationApi.PostAuthorizationSubjectBulkadd(roleId, roleDivPairsToGrants(grantsToAdd), subjectType)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to add role grants for subject %s: %s", roleId, err), resp)
}
return nil, nil
})
if diagErr != nil {
return resp, fmt.Errorf("error in adding grants: %v", diagErr)
}
}
return resp, nil
}
func getAssignedGrants(subjectID string, p *groupRolesProxy) ([]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error) {
var grants []platformclientv2.Authzgrant
subject, resp, err := p.authorizationApi.GetAuthorizationSubject(subjectID, true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get current grants for subject %s: %s", subjectID, err)
}
if subject != nil && subject.Grants != nil {
for _, grant := range *subject.Grants {
if grant.SubjectId != nil && *grant.SubjectId == subjectID {
grants = append(grants, grant)
}
}
}
return grants, resp, nil
}
package group_roles
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The resource_genesyscloud_group_roles.go contains all the methods that perform the core logic for a resource
*/
func createGroupRoles(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
groupID := d.Get("group_id").(string)
d.SetId(groupID)
return updateGroupRoles(ctx, d, meta)
}
func deleteGroupRoles(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
// Does not delete groups or roles. This resource will just no longer manage roles.
return nil
}
// readGroupRoles is used by the group_roles resource to read Group Roles from the genesys cloud
func readGroupRoles(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getGroupRolesProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceGroupRoles(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading roles for group %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
d.Set("group_id", d.Id())
roles, resp, err := flattenSubjectRoles(d, proxy)
if err != nil {
if util.IsStatus404ByInt(resp.StatusCode) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read roles for group %s | error: %v", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read roles for group %s | error: %v", d.Id(), err), resp))
}
d.Set("roles", roles)
log.Printf("Read roles for group %s", d.Id())
return cc.CheckState(d)
})
}
func updateGroupRoles(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getGroupRolesProxy(sdkConfig)
if !d.HasChange("roles") {
return nil
}
rolesConfig := d.Get("roles").(*schema.Set)
if rolesConfig == nil {
return nil
}
log.Printf("Updating roles for group %s", d.Id())
resp, diagErr := proxy.updateGroupRoles(ctx, d.Id(), rolesConfig, "PC_GROUP")
if diagErr != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update group role %s error: %s", d.Id(), diagErr), resp)
}
log.Printf("Updated group roles %v", d.Id())
return readGroupRoles(ctx, d, meta)
}
package group_roles
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_group_roles"
// SetRegistrar registers all the resources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterResource(resourceName, ResourceGroupRoles())
l.RegisterExporter(resourceName, GroupRolesExporter())
}
var (
RoleAssignmentResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"role_id": {
Description: "Role ID.",
Type: schema.TypeString,
Required: true,
},
"division_ids": {
Description: "Division IDs applied to this resource. If not set, the home division will be used. '*' may be set for all divisions.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
// ResourceGroupRoles registers the genesyscloud_group_roles resource with Terraform
func ResourceGroupRoles() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Group Roles maintains group role assignments.`,
CreateContext: provider.CreateWithPooledClient(createGroupRoles),
ReadContext: provider.ReadWithPooledClient(readGroupRoles),
UpdateContext: provider.UpdateWithPooledClient(updateGroupRoles),
DeleteContext: provider.DeleteWithPooledClient(deleteGroupRoles),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"group_id": {
Description: "Group ID that will be managed by this resource. Changing the group_id attribute for the groups_role object will cause the existing group_roles object to be dropped and recreated with a new ID",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"roles": {
Description: "Roles and their divisions assigned to this group.",
Type: schema.TypeSet,
Optional: true,
Elem: RoleAssignmentResource,
},
},
}
}
// GroupRolesExporter returns the resourceExporter object used to hold the genesyscloud_group_roles exporter's config
func GroupRolesExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllGroups),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"group_id": {RefType: "genesyscloud_group"},
"roles.role_id": {RefType: "genesyscloud_auth_role"},
"roles.division_ids": {RefType: "genesyscloud_auth_division", AltValues: []string{"*"}},
},
RemoveIfMissing: map[string][]string{
"roles": {"role_id"},
},
}
}
// Duplicated this from the group package to break a cyclical dependency. We should be asking ourselves why we are doing this at some point
func getAllGroups(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
groupsAPI := platformclientv2.NewGroupsApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
groups, resp, getErr := groupsAPI.GetGroups(pageSize, pageNum, nil, nil, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of groups error: %s", getErr), resp)
}
if groups.Entities == nil || len(*groups.Entities) == 0 {
break
}
for _, group := range *groups.Entities {
resources[*group.Id] = &resourceExporter.ResourceMeta{Name: *group.Name}
}
}
return resources, nil
}
package group_roles
import (
"fmt"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func flattenSubjectRoles(d *schema.ResourceData, p *groupRolesProxy) (*schema.Set, *platformclientv2.APIResponse, error) {
grants, resp, diagErr := getAssignedGrants(d.Id(), p)
if diagErr != nil {
return nil, resp, fmt.Errorf("error getting assigned grants %s", diagErr)
}
homeDivId, err := util.GetHomeDivisionID()
if err != nil {
return nil, nil, fmt.Errorf("error getting home division id %v", err)
}
roleDivsMap := make(map[string]*schema.Set)
for _, grant := range grants {
if currentDivs, ok := roleDivsMap[*grant.Role.Id]; ok {
currentDivs.Add(*grant.Division.Id)
} else {
roleDivsMap[*grant.Role.Id] = schema.NewSet(schema.HashString, []interface{}{*grant.Division.Id})
}
}
roleSet := schema.NewSet(schema.HashResource(RoleAssignmentResource), []interface{}{})
for roleID, divs := range roleDivsMap {
role := make(map[string]interface{})
role["role_id"] = roleID
role["division_ids"] = addDivisionIdsSetToRole(d, divs, roleID, homeDivId)
roleSet.Add(role)
}
return roleSet, resp, nil
}
func roleDivPairsToGrants(grantPairs []string) platformclientv2.Roledivisiongrants {
grants := make([]platformclientv2.Roledivisionpair, len(grantPairs))
for i, pair := range grantPairs {
roleDiv := strings.Split(pair, ":")
grants[i] = platformclientv2.Roledivisionpair{
RoleId: &roleDiv[0],
DivisionId: &roleDiv[1],
}
}
return platformclientv2.Roledivisiongrants{
Grants: &grants,
}
}
func addDivisionIdsSetToRole(d *schema.ResourceData, divIdsFromApi *schema.Set, roleId, homeDivId string) *schema.Set {
rolesSet, ok := d.Get("roles").(*schema.Set)
if !ok {
return divIdsFromApi
}
rolesMaps := rolesSet.List()
for _, role := range rolesMaps {
roleMap, ok := role.(map[string]interface{})
// find the role in question
if !ok || roleMap["role_id"].(string) != roleId {
continue
}
divs := roleMap["division_ids"].(*schema.Set)
for _, div := range divs.List() {
// home division id was included in original config -> use division_ids read from API
if div.(string) == homeDivId {
return divIdsFromApi
}
}
// home division ID was not included in original config for this role -> keep it out
divIdsFromApi.Remove(homeDivId)
break
}
return divIdsFromApi
}
// getExistingAndConfigGrants is used to generate the existing and config grants for the resource
func getExistingAndConfigGrants(grants []platformclientv2.Authzgrant, rolesConfig *schema.Set) ([]string, []string, error) {
rolesList := rolesConfig.List()
var existingGrants []string
for _, grant := range grants {
existingGrants = append(existingGrants, createRoleDivisionPair(*grant.Role.Id, *grant.Division.Id))
}
var configGrants []string
homeDiv, err := util.GetHomeDivisionID()
if err != nil {
return nil, nil, fmt.Errorf("failed to get home division ID %v", err)
}
for _, configRole := range rolesList {
roleMap := configRole.(map[string]interface{})
roleID := roleMap["role_id"].(string)
var divisionIDs []string
if configDivs, ok := roleMap["division_ids"].(*schema.Set); ok {
divisionIDs = *lists.SetToStringList(configDivs)
}
if len(divisionIDs) == 0 {
// No division set. Use the home division
divisionIDs = []string{homeDiv}
}
for _, divID := range divisionIDs {
configGrants = append(configGrants, createRoleDivisionPair(roleID, divID))
}
}
if err != nil {
return nil, nil, fmt.Errorf("failed to load grants: %v", err)
}
return existingGrants, configGrants, nil
}
func getGrantsToAddAndRemove(existingGrants []string, configGrants []string) ([]string, []string) {
grantsToRemove := lists.SliceDifference(existingGrants, configGrants)
grantsToAdd := lists.SliceDifference(configGrants, existingGrants)
return grantsToRemove, grantsToAdd
}
func createRoleDivisionPair(roleID string, divisionID string) string {
return roleID + ":" + divisionID
}
package idp_salesforce
import (
"context"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_idp_salesforce_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *idpSalesforceProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getIdpSalesforceFunc func(ctx context.Context, p *idpSalesforceProxy) (salesforce *platformclientv2.Salesforce, resp *platformclientv2.APIResponse, err error)
type updateIdpSalesforceFunc func(ctx context.Context, p *idpSalesforceProxy, salesforce *platformclientv2.Salesforce) (*platformclientv2.Identityprovider, *platformclientv2.APIResponse, error)
type deleteIdpSalesforceFunc func(ctx context.Context, p *idpSalesforceProxy) (resp *platformclientv2.APIResponse, err error)
// idpSalesforceProxy contains all of the methods that call genesys cloud APIs.
type idpSalesforceProxy struct {
clientConfig *platformclientv2.Configuration
identityProviderApi *platformclientv2.IdentityProviderApi
getIdpSalesforceAttr getIdpSalesforceFunc
updateIdpSalesforceAttr updateIdpSalesforceFunc
deleteIdpSalesforceAttr deleteIdpSalesforceFunc
}
// newIdpSalesforceProxy initializes the idp salesforce proxy with all of the data needed to communicate with Genesys Cloud
func newIdpSalesforceProxy(clientConfig *platformclientv2.Configuration) *idpSalesforceProxy {
api := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig)
return &idpSalesforceProxy{
clientConfig: clientConfig,
identityProviderApi: api,
getIdpSalesforceAttr: getIdpSalesforceFn,
updateIdpSalesforceAttr: updateIdpSalesforceFn,
deleteIdpSalesforceAttr: deleteIdpSalesforceFn,
}
}
// getIdpSalesforceProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getIdpSalesforceProxy(clientConfig *platformclientv2.Configuration) *idpSalesforceProxy {
if internalProxy == nil {
internalProxy = newIdpSalesforceProxy(clientConfig)
}
return internalProxy
}
// getIdpSalesforce returns a single Genesys Cloud idp salesforce
func (p *idpSalesforceProxy) getIdpSalesforce(ctx context.Context) (idpSalesforce *platformclientv2.Salesforce, resp *platformclientv2.APIResponse, err error) {
return p.getIdpSalesforceAttr(ctx, p)
}
// updateIdpSalesforce updates a Genesys Cloud idp salesforce
func (p *idpSalesforceProxy) updateIdpSalesforce(ctx context.Context, idpSalesforce *platformclientv2.Salesforce) (*platformclientv2.Identityprovider, *platformclientv2.APIResponse, error) {
return p.updateIdpSalesforceAttr(ctx, p, idpSalesforce)
}
// deleteIdpSalesforce deletes a Genesys Cloud idp salesforce by Id
func (p *idpSalesforceProxy) deleteIdpSalesforce(ctx context.Context) (resp *platformclientv2.APIResponse, err error) {
return p.deleteIdpSalesforceAttr(ctx, p)
}
// getIdpSalesforceFn is an implementation of the function to get a Genesys Cloud idp salesforce
func getIdpSalesforceFn(ctx context.Context, p *idpSalesforceProxy) (idpSalesforce *platformclientv2.Salesforce, resp *platformclientv2.APIResponse, err error) {
salesforce, resp, err := p.identityProviderApi.GetIdentityprovidersSalesforce()
if err != nil {
return nil, resp, err
}
return salesforce, resp, err
}
// updateIdpSalesforceFn is an implementation of the function to update a Genesys Cloud idp salesforce
func updateIdpSalesforceFn(ctx context.Context, p *idpSalesforceProxy, idpSalesforce *platformclientv2.Salesforce) (*platformclientv2.Identityprovider, *platformclientv2.APIResponse, error) {
salesForce, resp, err := p.identityProviderApi.PutIdentityprovidersSalesforce(*idpSalesforce)
if err != nil {
return nil, resp, err
}
return salesForce, resp, nil
}
// deleteIdpSalesforceFn is an implementation function for deleting a Genesys Cloud idp salesforce
func deleteIdpSalesforceFn(ctx context.Context, p *idpSalesforceProxy) (resp *platformclientv2.APIResponse, err error) {
_, resp, err = p.identityProviderApi.DeleteIdentityprovidersSalesforce()
return resp, err
}
package idp_salesforce
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
)
/*
The resource_genesyscloud_idp_salesforce.go contains all of the methods that perform the core logic for a resource.
*/
func getAllIdpSalesforce(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getIdpSalesforceProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
_, resp, getErr := proxy.getIdpSalesforce(ctx)
if getErr != nil {
if util.IsStatus404(resp) {
// Don't export if config doesn't exist
return resources, nil
}
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get IDP Salesforce error: %s", getErr), resp)
}
resources["0"] = &resourceExporter.ResourceMeta{Name: "salesforce"}
return resources, nil
}
func createIdpSalesforce(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating IDP Salesforce")
d.SetId("salesforce")
return updateIdpSalesforce(ctx, d, meta)
}
func readIdpSalesforce(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getIdpSalesforceProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpSalesforce(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading IDP Salesforce")
return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError {
salesforce, resp, getErr := proxy.getIdpSalesforce(ctx)
if getErr != nil {
if util.IsStatus404(resp) {
createIdpSalesforce(ctx, d, meta)
return retry.RetryableError(fmt.Errorf("Failed to read IDP Salesforce: %s", getErr))
}
return retry.NonRetryableError(fmt.Errorf("Failed to read IDP Salesforce: %s", getErr))
}
if salesforce.Certificate != nil {
_ = d.Set("certificates", lists.StringListToInterfaceList([]string{*salesforce.Certificate}))
} else if salesforce.Certificates != nil {
_ = d.Set("certificates", lists.StringListToInterfaceList(*salesforce.Certificates))
} else {
_ = d.Set("certificates", nil)
}
resourcedata.SetNillableValue(d, "issuer_uri", salesforce.IssuerURI)
resourcedata.SetNillableValue(d, "target_uri", salesforce.SsoTargetURI)
resourcedata.SetNillableValue(d, "disabled", salesforce.Disabled)
log.Printf("Read IDP Salesforce")
return cc.CheckState(d)
})
}
func updateIdpSalesforce(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getIdpSalesforceProxy(sdkConfig)
log.Printf("Updating IDP Salesforce")
update := platformclientv2.Salesforce{
IssuerURI: platformclientv2.String(d.Get("issuer_uri").(string)),
Disabled: platformclientv2.Bool(d.Get("disabled").(bool)),
}
if targetUri := d.Get("target_uri").(string); targetUri != "" {
update.SsoTargetURI = &targetUri
}
certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates")
if certificates != nil {
if len(*certificates) == 1 {
update.Certificate = &(*certificates)[0]
}
update.Certificates = certificates
}
_, resp, err := proxy.updateIdpSalesforce(ctx, &update)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update IDP Salesforce %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated IDP Salesforce")
return readIdpSalesforce(ctx, d, meta)
}
func deleteIdpSalesforce(ctx context.Context, _ *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getIdpSalesforceProxy(sdkConfig)
log.Printf("Deleting IDP Salesforce")
resp, err := proxy.deleteIdpSalesforce(ctx)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete IDP Salesforce error: %s", err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getIdpSalesforce(ctx)
if err != nil {
if util.IsStatus404(resp) {
// IDP Salesforce deleted
log.Printf("Deleted Salesforce Ping")
return nil
}
return retry.NonRetryableError(fmt.Errorf("Error deleting IDP Salesforce: %s", err))
}
return retry.RetryableError(fmt.Errorf("IDP Salesforce still exists"))
})
}
package idp_salesforce
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"time"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_idp_salesforce_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the idp_salesforce resource.
3. The datasource schema definitions for the idp_salesforce datasource.
4. The resource exporter configuration for the idp_salesforce exporter.
*/
const resourceName = "genesyscloud_idp_salesforce"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceIdpSalesforce())
regInstance.RegisterExporter(resourceName, IdpSalesforceExporter())
}
// ResourceIdpSalesforce registers the genesyscloud_idp_salesforce resource with Terraform
func ResourceIdpSalesforce() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Single Sign-on Salesforce Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-salesforce-as-a-single-sign-on-provider/",
CreateContext: provider.CreateWithPooledClient(createIdpSalesforce),
ReadContext: provider.ReadWithPooledClient(readIdpSalesforce),
UpdateContext: provider.UpdateWithPooledClient(updateIdpSalesforce),
DeleteContext: provider.DeleteWithPooledClient(deleteIdpSalesforce),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"certificates": {
Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"issuer_uri": {
Description: "Issuer URI provided by Salesforce.",
Type: schema.TypeString,
Required: true,
},
"target_uri": {
Description: "Target URI provided by Salesforce.",
Type: schema.TypeString,
Optional: true,
},
"disabled": {
Description: "True if Salesforce is disabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func IdpSalesforceExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpSalesforce),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
package integration
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_integration.go contains the data source implementation
for the resource.
Note: This code should contain no code for doing the actual lookup in Genesys Cloud. Instead,
it should be added to the _proxy.go file for the class using our proxy pattern.
*/
// dataSourceIntegrationRead retrieves by name the integration id in question
func dataSourceIntegrationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationsProxy(sdkConfig)
integrationName := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
integration, retryable, resp, err := ip.getIntegrationByName(ctx, integrationName)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get page of integrations: %s | error: %s", integrationName, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get integration %s", integrationName), resp))
}
d.SetId(*integration.Id)
return nil
})
}
package integration
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_integration_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *integrationsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllIntegrationsFunc func(ctx context.Context, p *integrationsProxy) (*[]platformclientv2.Integration, *platformclientv2.APIResponse, error)
type createIntegrationFunc func(ctx context.Context, p *integrationsProxy, integration *platformclientv2.Createintegrationrequest) (*platformclientv2.Integration, *platformclientv2.APIResponse, error)
type getIntegrationByIdFunc func(ctx context.Context, p *integrationsProxy, integrationId string) (integration *platformclientv2.Integration, response *platformclientv2.APIResponse, err error)
type getIntegrationByNameFunc func(ctx context.Context, p *integrationsProxy, integrationName string) (integration *platformclientv2.Integration, retryable bool, response *platformclientv2.APIResponse, err error)
type updateIntegrationFunc func(ctx context.Context, p *integrationsProxy, integrationId string, integration *platformclientv2.Integration) (*platformclientv2.Integration, *platformclientv2.APIResponse, error)
type deleteIntegrationFunc func(ctx context.Context, p *integrationsProxy, integrationId string) (response *platformclientv2.APIResponse, err error)
type getIntegrationConfigFunc func(ctx context.Context, p *integrationsProxy, integrationId string) (config *platformclientv2.Integrationconfiguration, response *platformclientv2.APIResponse, err error)
type updateIntegrationConfigFunc func(ctx context.Context, p *integrationsProxy, integrationId string, integrationConfig *platformclientv2.Integrationconfiguration) (integration *platformclientv2.Integrationconfiguration, response *platformclientv2.APIResponse, err error)
// integrationProxy contains all of the methods that call genesys cloud APIs.
type integrationsProxy struct {
clientConfig *platformclientv2.Configuration
integrationsApi *platformclientv2.IntegrationsApi
getAllIntegrationsAttr getAllIntegrationsFunc
createIntegrationAttr createIntegrationFunc
getIntegrationByIdAttr getIntegrationByIdFunc
getIntegrationByNameAttr getIntegrationByNameFunc
updateIntegrationAttr updateIntegrationFunc
updateIntegrationConfigAttr updateIntegrationConfigFunc
deleteIntegrationAttr deleteIntegrationFunc
getIntegrationConfigAttr getIntegrationConfigFunc
}
// newIntegrationsProxy initializes the Integrations proxy with all of the data needed to communicate with Genesys Cloud
func newIntegrationsProxy(clientConfig *platformclientv2.Configuration) *integrationsProxy {
api := platformclientv2.NewIntegrationsApiWithConfig(clientConfig)
return &integrationsProxy{
clientConfig: clientConfig,
integrationsApi: api,
getAllIntegrationsAttr: getAllIntegrationsFn,
createIntegrationAttr: createIntegrationFn,
getIntegrationByIdAttr: getIntegrationByIdFn,
getIntegrationByNameAttr: getIntegrationByNameFn,
updateIntegrationAttr: updateIntegrationFn,
updateIntegrationConfigAttr: updateIntegrationConfigFn,
deleteIntegrationAttr: deleteIntegrationFn,
getIntegrationConfigAttr: getIntegrationConfigFn,
}
}
// getIntegrationsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getIntegrationsProxy(clientConfig *platformclientv2.Configuration) *integrationsProxy {
if internalProxy == nil {
internalProxy = newIntegrationsProxy(clientConfig)
}
return internalProxy
}
// getAllIntegrations retrieves all Genesys Cloud Integrations
func (p *integrationsProxy) getAllIntegrations(ctx context.Context) (*[]platformclientv2.Integration, *platformclientv2.APIResponse, error) {
return p.getAllIntegrationsAttr(ctx, p)
}
// createIntegration creates a Genesys Cloud Integration
func (p *integrationsProxy) createIntegration(ctx context.Context, integrationReq *platformclientv2.Createintegrationrequest) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
return p.createIntegrationAttr(ctx, p, integrationReq)
}
// getIntegrationById gets Genesys Cloud Integration by id
func (p *integrationsProxy) getIntegrationById(ctx context.Context, integrationId string) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
return p.getIntegrationByIdAttr(ctx, p, integrationId)
}
// getIntegrationByName gets a Genesys Cloud Integration by name
func (p *integrationsProxy) getIntegrationByName(ctx context.Context, integrationName string) (*platformclientv2.Integration, bool, *platformclientv2.APIResponse, error) {
return p.getIntegrationByNameAttr(ctx, p, integrationName)
}
// updateIntegration updates a Genesys Cloud Integration
func (p *integrationsProxy) updateIntegration(ctx context.Context, integrationId string, integration *platformclientv2.Integration) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
return p.updateIntegrationAttr(ctx, p, integrationId, integration)
}
// deleteIntegration deletes a Genesys Cloud Integration
func (p *integrationsProxy) deleteIntegration(ctx context.Context, integrationId string) (response *platformclientv2.APIResponse, err error) {
return p.deleteIntegrationAttr(ctx, p, integrationId)
}
// getIntegrationConfig get the current config of a Genesys Cloud Integration
func (p *integrationsProxy) getIntegrationConfig(ctx context.Context, integrationId string) (*platformclientv2.Integrationconfiguration, *platformclientv2.APIResponse, error) {
return p.getIntegrationConfigAttr(ctx, p, integrationId)
}
// updateIntegrationConfig updates the config of a Genesys Cloud Integration
func (p *integrationsProxy) updateIntegrationConfig(ctx context.Context, integrationId string, integrationConfig *platformclientv2.Integrationconfiguration) (*platformclientv2.Integrationconfiguration, *platformclientv2.APIResponse, error) {
return p.updateIntegrationConfigAttr(ctx, p, integrationId, integrationConfig)
}
// getAllIntegrationsFn is the implementation for retrieving all integrations in Genesys Cloud
func getAllIntegrationsFn(ctx context.Context, p *integrationsProxy) (*[]platformclientv2.Integration, *platformclientv2.APIResponse, error) {
var allIntegrations []platformclientv2.Integration
var resp *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
integrations, response, err := p.integrationsApi.GetIntegrations(pageSize, pageNum, "", nil, "", "")
if err != nil {
return nil, resp, err
}
resp = response
if integrations.Entities == nil || len(*integrations.Entities) == 0 {
break
}
allIntegrations = append(allIntegrations, *integrations.Entities...)
}
return &allIntegrations, resp, nil
}
// createIntegrationFn is the implementation for creating an integration in Genesys Cloud
func createIntegrationFn(ctx context.Context, p *integrationsProxy, integrationReq *platformclientv2.Createintegrationrequest) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
integration, resp, err := p.integrationsApi.PostIntegrations(*integrationReq)
if err != nil {
return nil, resp, err
}
return integration, resp, nil
}
// getIntegrationByIdFn is the implementation for getting a Genesys Cloud Integration by id
func getIntegrationByIdFn(ctx context.Context, p *integrationsProxy, integrationId string) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
const pageSize = 100
const pageNum = 1
integration, resp, err := p.integrationsApi.GetIntegration(integrationId, pageSize, pageNum, "", nil, "", "")
if err != nil {
return nil, resp, err
}
return integration, resp, nil
}
// getIntegrationByNameFn is the implementation for getting a Genesys Cloud Integration by name
func getIntegrationByNameFn(ctx context.Context, p *integrationsProxy, integrationName string) (*platformclientv2.Integration, bool, *platformclientv2.APIResponse, error) {
var foundIntegration *platformclientv2.Integration
var resp *platformclientv2.APIResponse
const pageSize = 100
for pageNum := 1; ; pageNum++ {
integrations, response, err := p.integrationsApi.GetIntegrations(pageSize, pageNum, "", nil, "", "")
if err != nil {
return nil, false, resp, err
}
resp = response
if integrations.Entities == nil || len(*integrations.Entities) == 0 {
return nil, true, resp, fmt.Errorf("no integrations found with name: %s", integrationName)
}
for _, integration := range *integrations.Entities {
if integration.Name != nil && *integration.Name == integrationName {
foundIntegration = &integration
break
}
}
if foundIntegration != nil {
break
}
}
return foundIntegration, false, resp, nil
}
// updateIntegrationFn is the implementation for updating a Genesys Cloud Integration
func updateIntegrationFn(ctx context.Context, p *integrationsProxy, integrationId string, integration *platformclientv2.Integration) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
const pageSize = 25
const pageNum = 1
integration, resp, err := p.integrationsApi.PatchIntegration(integrationId, pageSize, pageNum, "", nil, "", "", *integration)
if err != nil {
return nil, resp, err
}
return integration, resp, nil
}
// deleteIntegrationFn is the implementation for deleting a Genesys Cloud Integration
func deleteIntegrationFn(ctx context.Context, p *integrationsProxy, integrationId string) (response *platformclientv2.APIResponse, err error) {
_, resp, err := p.integrationsApi.DeleteIntegration(integrationId)
if err != nil {
return resp, err
}
return resp, nil
}
// getIntegrationConfigFn is the implementation for getting the current config of a Genesys Cloud Integration
func getIntegrationConfigFn(ctx context.Context, p *integrationsProxy, integrationId string) (*platformclientv2.Integrationconfiguration, *platformclientv2.APIResponse, error) {
config, resp, err := p.integrationsApi.GetIntegrationConfigCurrent(integrationId)
if err != nil {
return nil, resp, err
}
return config, resp, nil
}
// updateIntegrationConfigFn is the implementation for updating a Genesys Cloud Integration Config
func updateIntegrationConfigFn(ctx context.Context, p *integrationsProxy, integrationId string, integrationConfig *platformclientv2.Integrationconfiguration) (*platformclientv2.Integrationconfiguration, *platformclientv2.APIResponse, error) {
config, resp, err := p.integrationsApi.PutIntegrationConfigCurrent(integrationId, *integrationConfig)
if err != nil {
return nil, resp, err
}
return config, resp, nil
}
package integration
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_integration.go contains all of the methods that perform the core logic for a resource.
In general a resource should have a approximately 5 methods in it:
1. A getAll.... function that the CX as Code exporter will use during the process of exporting Genesys Cloud.
2. A create.... function that the resource will use to create a Genesys Cloud object (e.g. genesycloud_integration)
3. A read.... function that looks up a single resource.
4. An update... function that updates a single resource.
5. A delete.... function that deletes a single resource.
Two things to note:
1. All code in these methods should be focused on getting data in and out of Terraform. All code that is used for interacting
with a Genesys API should be encapsulated into a proxy class contained within the package.
2. In general, to keep this file somewhat manageable, if you find yourself with a number of helper functions move them to a
utils function in the package. This will keep the code manageable and easy to work through.
*/
// getAllIntegrations retrieves all of the integrations via Terraform in the Genesys Cloud and is used for the exporter
func getAllIntegrations(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
ip := getIntegrationsProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
integrations, resp, err := ip.getAllIntegrations(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get all integrations %s", err), resp)
}
for _, integration := range *integrations {
log.Printf("Dealing with integration id : %s", *integration.Id)
resources[*integration.Id] = &resourceExporter.ResourceMeta{Name: *integration.Name}
}
return resources, nil
}
// createIntegration is used by the integrations resource to create Genesyscloud integration
func createIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
intendedState := d.Get("intended_state").(string)
integrationType := d.Get("integration_type").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationsProxy(sdkConfig)
createIntegrationReq := &platformclientv2.Createintegrationrequest{
IntegrationType: &platformclientv2.Integrationtype{
Id: &integrationType,
},
}
integration, resp, err := ip.createIntegration(ctx, createIntegrationReq)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create integration error: %s", err), resp)
}
d.SetId(*integration.Id)
//Update integration config separately
diagErr, name := updateIntegrationConfigFromResourceData(ctx, d, ip)
if diagErr != nil {
return diagErr
}
// Set attributes that can only be modified in a patch
if d.HasChange("intended_state") {
log.Printf("Updating additional attributes for integration %s", name)
_, resp, patchErr := ip.updateIntegration(ctx, d.Id(), &platformclientv2.Integration{
IntendedState: &intendedState,
})
if patchErr != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update integration %s error: %s", d.Id(), err), resp)
}
}
log.Printf("Created integration %s %s", name, *integration.Id)
return readIntegration(ctx, d, meta)
}
// readIntegration is used by the integration resource to read an integration from genesys cloud.
func readIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationsProxy(sdkConfig)
log.Printf("Reading integration %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
currentIntegration, resp, getErr := ip.getIntegrationById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration %s | error: %s", d.Id(), getErr), resp))
}
d.Set("integration_type", *currentIntegration.IntegrationType.Id)
resourcedata.SetNillableValue(d, "intended_state", currentIntegration.IntendedState)
// Use returned ID to get current config, which contains complete configuration
integrationConfig, resp, err := ip.getIntegrationConfig(ctx, *currentIntegration.Id)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read config of integration %s | error: %s", d.Id(), getErr), resp))
}
d.Set("config", flattenIntegrationConfig(integrationConfig))
log.Printf("Read integration %s %s", d.Id(), *currentIntegration.Name)
return nil
})
}
// updateIntegration is used by the integration resource to update an integration in Genesys Cloud
func updateIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
intendedState := d.Get("intended_state").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationsProxy(sdkConfig)
diagErr, name := updateIntegrationConfigFromResourceData(ctx, d, ip)
if diagErr != nil {
return diagErr
}
if d.HasChange("intended_state") {
log.Printf("Updating integration %s", name)
_, resp, patchErr := ip.updateIntegration(ctx, d.Id(), &platformclientv2.Integration{
IntendedState: &intendedState,
})
if patchErr != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Integration %s %s", d.Id(), patchErr), resp)
}
}
log.Printf("Updated integration %s %s", name, d.Id())
return readIntegration(ctx, d, meta)
}
// deleteIntegration is used by the integration resource to delete an integration from Genesys cloud.
func deleteIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationsProxy(sdkConfig)
resp, err := ip.deleteIntegration(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Integration %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := ip.getIntegrationById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Integration deleted
log.Printf("Deleted Integration %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting integration %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("integration %s still exists", d.Id()), resp))
})
}
package integration
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
/*
resource_genesyscloud_integration_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the integration resource.
3. The datasource schema definitions for the integration datasource.
4. The resource exporter configuration for the integration exporter.
*/
const resourceName = "genesyscloud_integration"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceIntegration())
l.RegisterResource(resourceName, ResourceIntegration())
l.RegisterExporter(resourceName, IntegrationExporter())
}
// ResourceIntegration registers the genesyscloud_integration resource with Terraform
func ResourceIntegration() *schema.Resource {
integrationConfigResource := &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Integration name.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"notes": {
Description: "Integration notes.",
Type: schema.TypeString,
Optional: true,
},
"properties": {
Description: "Integration config properties (JSON string).",
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"advanced": {
Description: "Integration advanced config (JSON string).",
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"credentials": {
Description: "Credentials required for the integration. The required keys are indicated in the credentials property of the Integration Type.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
return &schema.Resource{
Description: "Genesys Cloud Integration",
CreateContext: provider.CreateWithPooledClient(createIntegration),
ReadContext: provider.ReadWithPooledClient(readIntegration),
UpdateContext: provider.UpdateWithPooledClient(updateIntegration),
DeleteContext: provider.DeleteWithPooledClient(deleteIntegration),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"intended_state": {
Description: "Integration state (ENABLED | DISABLED | DELETED).",
Type: schema.TypeString,
Optional: true,
Default: "DISABLED",
ValidateFunc: validation.StringInSlice([]string{"ENABLED", "DISABLED", "DELETED"}, false),
},
"integration_type": {
Description: "Integration type.",
Type: schema.TypeString,
Required: true,
},
"config": {
Description: "Integration config. Each integration type has different schema, use [GET /api/v2/integrations/types/{typeId}/configschemas/{configType}](https://developer.mypurecloud.com/api/rest/v2/integrations/#get-api-v2-integrations-types--typeId--configschemas--configType-) to check schema, then use the correct attribute names for properties.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: integrationConfigResource,
},
},
}
}
// IntegrationExporter returns the resourceExporter object used to hold the genesyscloud_integration exporter's config
func IntegrationExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIntegrations),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"config.credentials.*": {RefType: "genesyscloud_integration_credential"},
},
JsonEncodeAttributes: []string{"config.properties", "config.advanced"},
EncodedRefAttrs: map[*resourceExporter.JsonEncodeRefAttr]*resourceExporter.RefAttrSettings{
{Attr: "config.properties", NestedAttr: "groups"}: {RefType: "genesyscloud_group"},
},
}
}
// DataSourceIntegration registers the genesyscloud_integration data source
func DataSourceIntegration() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud integration. Select an integration by name",
ReadContext: provider.ReadWithPooledClient(dataSourceIntegrationRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the integration",
Type: schema.TypeString,
Required: true,
},
},
}
}
package integration
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_integration_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
Note: Look for opportunities to minimize boilerplate code using functions and Generics
*/
// flattenIntegrationConfig converts a platformclientv2.Integrationconfiguration into a map and then into single-element array for consumption by Terraform
func flattenIntegrationConfig(config *platformclientv2.Integrationconfiguration) []interface{} {
if config == nil {
return nil
}
var (
configName string
configNotes string
configProperties string
configAdvanced string
configCredentials map[string]interface{}
)
if config.Name != nil {
configName = *config.Name
}
if config.Notes != nil {
if *config.Notes == "node_dynamodb_empty_string" {
*config.Notes = ""
}
configNotes = *config.Notes
}
if config.Properties != nil {
propJSONStr, err := json.Marshal(*config.Properties)
if err != nil {
log.Printf("Failed to marshal integration config properties. Error message: %s", err)
} else {
configProperties = string(propJSONStr)
}
}
if config.Advanced != nil {
advJSONStr, err := json.Marshal(*config.Advanced)
if err != nil {
log.Printf("Failed to marshal integration config advanced properties. Error message: %s", err)
} else {
configAdvanced = string(advJSONStr)
}
}
if config.Credentials != nil {
configCredentials = flattenConfigCredentials(*config.Credentials)
}
return []interface{}{map[string]interface{}{
"name": configName,
"notes": configNotes,
"properties": configProperties,
"advanced": configAdvanced,
"credentials": configCredentials,
}}
}
// flattenConfigCredentials converts a map of platformclientv2.Credentialinfo into a map of only the credential IDs for consumption by Terraform
func flattenConfigCredentials(credentials map[string]platformclientv2.Credentialinfo) map[string]interface{} {
if len(credentials) == 0 {
return nil
}
results := make(map[string]interface{})
for k, v := range credentials {
results[k] = *v.Id
}
return results
}
// updateIntegrationConfigFromResourceData takes the integrationsProxy to update updates the config of an integration
// Returns a diag error and the name of the integration
func updateIntegrationConfigFromResourceData(ctx context.Context, d *schema.ResourceData, p *integrationsProxy) (diag.Diagnostics, string) {
if d.HasChange("config") {
if configInput := d.Get("config").([]interface{}); configInput != nil {
integrationConfig, resp, err := p.getIntegrationConfig(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get the integration config for integration %s before updating its config error: %s", d.Id(), err), resp), ""
}
name := *integrationConfig.Name
notes := *integrationConfig.Notes
propJSON := *integrationConfig.Properties
advJSON := *integrationConfig.Advanced
credential := *integrationConfig.Credentials
if len(configInput) > 0 {
configMap := configInput[0].(map[string]interface{})
if configMap["name"].(string) != "" {
name = configMap["name"].(string)
}
notes = configMap["notes"].(string)
if properties := configMap["properties"].(string); len(properties) > 0 {
if err := json.Unmarshal([]byte(properties), &propJSON); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to convert properties string to JSON for integration %s", d.Id()), err), name
}
}
if advanced := configMap["advanced"].(string); len(advanced) > 0 {
if err := json.Unmarshal([]byte(advanced), &advJSON); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to convert advanced property string to JSON for integration %s", d.Id()), err), name
}
}
credential = buildConfigCredentials(configMap["credentials"].(map[string]interface{}))
}
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get latest config version
integrationConfig, resp, err := p.getIntegrationConfig(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get the integration config for integration %s before updating its config. error: %s", d.Id(), err), resp)
}
_, resp, err = p.updateIntegrationConfig(ctx, d.Id(), &platformclientv2.Integrationconfiguration{
Name: &name,
Notes: ¬es,
Version: integrationConfig.Version,
Properties: &propJSON,
Advanced: &advJSON,
Credentials: &credential,
})
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update config for integration %s error: %s", d.Id(), err), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr, ""
}
}
}
return nil, ""
}
// buildConfigCredentials takes a map of credential IDs and turns it into a map of platformclientv2.Credentialinfo
func buildConfigCredentials(credentials map[string]interface{}) map[string]platformclientv2.Credentialinfo {
results := make(map[string]platformclientv2.Credentialinfo)
if len(credentials) > 0 {
for k, v := range credentials {
credID := v.(string)
results[k] = platformclientv2.Credentialinfo{Id: &credID}
}
return results
}
return results
}
// GenerateIntegrationResource builds the terraform string for creating an integration
func GenerateIntegrationResource(resourceID string, intendedState string, integrationType string, attrs ...string) string {
return fmt.Sprintf(`resource "genesyscloud_integration" "%s" {
intended_state = %s
integration_type = %s
%s
}
`, resourceID, intendedState, integrationType, strings.Join(attrs, "\n"))
}
func GenerateIntegrationConfig(name string, notes string, cred string, props string, adv string) string {
return fmt.Sprintf(`config {
name = %s
notes = %s
credentials = {
%s
}
properties = %s
advanced = %s
}
`, name, notes, cred, props, adv)
}
package integration_action
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_integration_action.go contains the data source implementation
for the resource.
Note: This code should contain no code for doing the actual lookup in Genesys Cloud. Instead,
it should be added to the _proxy.go file for the class using our proxy pattern.
*/
// dataSourceIntegrationActionRead retrieves by name the integration action id in question
func dataSourceIntegrationActionRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
iap := getIntegrationActionsProxy(sdkConfig)
actionName := d.Get("name").(string)
// Query for integration actions by name. Retry in case new action is not yet indexed by search.
// As action names are non-unique, fail in case of multiple results.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
actions, resp, err := iap.getIntegrationActionsByName(ctx, actionName)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting data action %s | error: %s", actionName, err), resp))
}
if len(*actions) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no data actions found with name %s", actionName), resp))
}
if len(*actions) > 1 {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("ambiguous data action name: %s", actionName), resp))
}
action := (*actions)[0]
d.SetId(*action.Id)
return nil
})
}
package integration_action
import (
"context"
"encoding/json"
"errors"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"net/http"
)
/*
The genesyscloud_integration_action_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
/*
NOTE: Most of the integration action methods invokes the API manually instead of using the Genesys Cloud Go SDK types
and API methods. This is due to the limitation of the output contract.
In the SDK the input and output contracts are of the Jsonschemadocument type. This defines a JSON schema
for the contract. The type has the usual properties like 'Name' and 'Properties' however it is missing the 'Items'
property which is needed to define the item type of an array.
In the API, the output contract allows the root to be of 'array' type instead of 'object'. If that is the case it requires
the 'Items' property to define the 'object' schema it allows. Since it's impossible to do with the SDK,
helper methods and types are created to invoke the APIs with Genesys Cloud.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *integrationActionsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllIntegrationActionsFunc func(ctx context.Context, p *integrationActionsProxy) (*[]platformclientv2.Action, *platformclientv2.APIResponse, error)
type createIntegrationActionFunc func(ctx context.Context, p *integrationActionsProxy, action *IntegrationAction) (*IntegrationAction, *platformclientv2.APIResponse, error)
type getIntegrationActionByIdFunc func(ctx context.Context, p *integrationActionsProxy, actionId string) (*IntegrationAction, *platformclientv2.APIResponse, error)
type getIntegrationActionsByNameFunc func(ctx context.Context, p *integrationActionsProxy, actionName string) (actions *[]platformclientv2.Action, response *platformclientv2.APIResponse, err error)
type updateIntegrationActionFunc func(ctx context.Context, p *integrationActionsProxy, actionId string, updateAction *platformclientv2.Updateactioninput) (*platformclientv2.Action, *platformclientv2.APIResponse, error)
type deleteIntegrationActionFunc func(ctx context.Context, p *integrationActionsProxy, actionId string) (*platformclientv2.APIResponse, error)
type getIntegrationActionTemplateFunc func(ctx context.Context, p *integrationActionsProxy, actionId string, fileName string) (*string, *platformclientv2.APIResponse, error)
// integrationActionsProxy contains all of the methods that call genesys cloud APIs.
type integrationActionsProxy struct {
clientConfig *platformclientv2.Configuration
integrationsApi *platformclientv2.IntegrationsApi
getAllIntegrationActionsAttr getAllIntegrationActionsFunc
createIntegrationActionAttr createIntegrationActionFunc
getIntegrationActionByIdAttr getIntegrationActionByIdFunc
getIntegrationActionsByNameAttr getIntegrationActionsByNameFunc
updateIntegrationActionAttr updateIntegrationActionFunc
deleteIntegrationActionAttr deleteIntegrationActionFunc
getIntegrationActionTemplateAttr getIntegrationActionTemplateFunc
}
// newIntegrationActionsProxy initializes the integrationActionsProxy with all of the data needed to communicate with Genesys Cloud
func newIntegrationActionsProxy(clientConfig *platformclientv2.Configuration) *integrationActionsProxy {
api := platformclientv2.NewIntegrationsApiWithConfig(clientConfig)
return &integrationActionsProxy{
clientConfig: clientConfig,
integrationsApi: api,
getAllIntegrationActionsAttr: getAllIntegrationActionsFn,
createIntegrationActionAttr: createIntegrationActionFn,
getIntegrationActionByIdAttr: getIntegrationActionByIdFn,
getIntegrationActionsByNameAttr: getIntegrationActionsByNameFn,
updateIntegrationActionAttr: updateIntegrationActionFn,
deleteIntegrationActionAttr: deleteIntegrationActionFn,
getIntegrationActionTemplateAttr: getIntegrationActionTemplateFn,
}
}
// getIntegrationActionsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getIntegrationActionsProxy(clientConfig *platformclientv2.Configuration) *integrationActionsProxy {
if internalProxy == nil {
internalProxy = newIntegrationActionsProxy(clientConfig)
}
return internalProxy
}
// getAllIntegrationActions retrieves all Genesys Cloud Integration Actions
func (p *integrationActionsProxy) getAllIntegrationActions(ctx context.Context) (*[]platformclientv2.Action, *platformclientv2.APIResponse, error) {
return p.getAllIntegrationActionsAttr(ctx, p)
}
// createIntegrationAction creates a Genesys Cloud Integration Action
func (p *integrationActionsProxy) createIntegrationAction(ctx context.Context, actionInput *IntegrationAction) (*IntegrationAction, *platformclientv2.APIResponse, error) {
return p.createIntegrationActionAttr(ctx, p, actionInput)
}
// getIntegrationActionById gets a Genesys Cloud Integration Action by id
func (p *integrationActionsProxy) getIntegrationActionById(ctx context.Context, actionId string) (action *IntegrationAction, response *platformclientv2.APIResponse, err error) {
return p.getIntegrationActionByIdAttr(ctx, p, actionId)
}
// getIntegrationActionsByName gets a Genesys Cloud Integration Action by name
func (p *integrationActionsProxy) getIntegrationActionsByName(ctx context.Context, actionName string) (actions *[]platformclientv2.Action, response *platformclientv2.APIResponse, err error) {
return p.getIntegrationActionsByNameAttr(ctx, p, actionName)
}
// updateIntegrationAction updates a Genesys Cloud Integration Action
func (p *integrationActionsProxy) updateIntegrationAction(ctx context.Context, actionId string, updateAction *platformclientv2.Updateactioninput) (*platformclientv2.Action, *platformclientv2.APIResponse, error) {
return p.updateIntegrationActionAttr(ctx, p, actionId, updateAction)
}
// deleteIntegrationAction deletes a Genesys Cloud Integration Action
func (p *integrationActionsProxy) deleteIntegrationAction(ctx context.Context, actionId string) (*platformclientv2.APIResponse, error) {
return p.deleteIntegrationActionAttr(ctx, p, actionId)
}
// getIntegrationActionTemplate gets a Genesys Cloud Integration Action Contract Template by its filename
func (p *integrationActionsProxy) getIntegrationActionTemplate(ctx context.Context, actionId string, fileName string) (*string, *platformclientv2.APIResponse, error) {
return p.getIntegrationActionTemplateAttr(ctx, p, actionId, fileName)
}
// getAllIntegrationActionsFn is the implementation for retrieving all integration actions in Genesys Cloud
func getAllIntegrationActionsFn(ctx context.Context, p *integrationActionsProxy) (*[]platformclientv2.Action, *platformclientv2.APIResponse, error) {
actions := []platformclientv2.Action{}
var resp *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
actionsList, response, err := p.integrationsApi.GetIntegrationsActions(pageSize, pageNum, "", "", "", "", "", "", "", "", "")
if err != nil {
return nil, resp, err
}
resp = response
if actionsList.Entities == nil || len(*actionsList.Entities) == 0 {
break
}
actions = append(actions, *actionsList.Entities...)
}
return &actions, resp, nil
}
// createIntegrationActionFn is the implementation for creating an integration action in Genesys Cloud
func createIntegrationActionFn(ctx context.Context, p *integrationActionsProxy, actionInput *IntegrationAction) (*IntegrationAction, *platformclientv2.APIResponse, error) {
action, resp, err := sdkPostIntegrationAction(actionInput, p.integrationsApi)
if err != nil {
return nil, resp, err
}
return action, resp, nil
}
// getIntegrationActionByIdFn is the implementation for getting an integration action by id in Genesys Cloud
func getIntegrationActionByIdFn(ctx context.Context, p *integrationActionsProxy, actionId string) (*IntegrationAction, *platformclientv2.APIResponse, error) {
action, resp, err := sdkGetIntegrationAction(actionId, p.integrationsApi)
if err != nil {
return nil, resp, err
}
return action, resp, nil
}
// getIntegrationActionsByNameFn is the implementation for getting an integration action by name in Genesys Cloud
func getIntegrationActionsByNameFn(ctx context.Context, p *integrationActionsProxy, actionName string) (*[]platformclientv2.Action, *platformclientv2.APIResponse, error) {
var actions []platformclientv2.Action
var resp *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
integrationAction, response, err := p.integrationsApi.GetIntegrationsActions(pageSize, pageNum, "", "", "", "", "", actionName, "", "", "")
if err != nil {
return nil, response, err
}
resp = response
if integrationAction.Entities == nil || len(*integrationAction.Entities) == 0 {
break
}
for _, action := range *integrationAction.Entities {
if action.Name != nil && *action.Name == actionName {
actions = append(actions, action)
}
}
}
return &actions, resp, nil
}
// updateIntegrationActionFn is the implementation for updating an integration action in Genesys Cloud
func updateIntegrationActionFn(ctx context.Context, p *integrationActionsProxy, actionId string, updateAction *platformclientv2.Updateactioninput) (*platformclientv2.Action, *platformclientv2.APIResponse, error) {
action, resp, err := p.integrationsApi.PatchIntegrationsAction(actionId, *updateAction)
if err != nil {
return nil, resp, err
}
return action, resp, nil
}
// deleteIntegrationActionFn is the implementation for deleting an integration action in Genesys Cloud
func deleteIntegrationActionFn(ctx context.Context, p *integrationActionsProxy, actionId string) (*platformclientv2.APIResponse, error) {
resp, err := p.integrationsApi.DeleteIntegrationsAction(actionId)
if err != nil {
return resp, err
}
return resp, nil
}
// getIntegrationActionTemplateFn is the implementation for getting the integration action template in Genesys Cloud
func getIntegrationActionTemplateFn(ctx context.Context, p *integrationActionsProxy, actionId string, fileName string) (*string, *platformclientv2.APIResponse, error) {
template, resp, err := sdkGetIntegrationActionTemplate(actionId, fileName, p.integrationsApi)
if err != nil {
return nil, resp, err
}
return template, resp, nil
}
// sdkPostIntegrationAction is the non-sdk helper method for creating an Integration Action
func sdkPostIntegrationAction(body *IntegrationAction, api *platformclientv2.IntegrationsApi) (*IntegrationAction, *platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/integrations/actions"
headerParams := make(map[string]string)
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *IntegrationAction
response, err := apiClient.CallAPI(path, http.MethodPost, body, headerParams, nil, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
}
return successPayload, response, err
}
// sdkGetIntegrationAction is the non-sdk helper method for getting an Integration Action
func sdkGetIntegrationAction(actionId string, api *platformclientv2.IntegrationsApi) (*IntegrationAction, *platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/integrations/actions/" + actionId
headerParams := make(map[string]string)
queryParams := make(map[string]string)
// oauth required
if api.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
}
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
queryParams["expand"] = "contract"
queryParams["includeConfig"] = "true"
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *IntegrationAction
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
}
return successPayload, response, err
}
// sdkGetIntegrationActionTemplate is the non-sdk helper method for getting an Integration Action Template
func sdkGetIntegrationActionTemplate(actionId, templateName string, api *platformclientv2.IntegrationsApi) (*string, *platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/integrations/actions/" + actionId + "/templates/" + templateName
headerParams := make(map[string]string)
queryParams := make(map[string]string)
// oauth required
if api.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
}
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "*/*"
var successPayload *string
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
templateStr := string(response.RawBody)
successPayload = &templateStr
}
return successPayload, response, err
}
package integration_action
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_integration_action.go contains all of the methods that perform the core logic for a resource.
In general a resource should have a approximately 5 methods in it:
1. A getAll.... function that the CX as Code exporter will use during the process of exporting Genesys Cloud.
2. A create.... function that the resource will use to create a Genesys Cloud object (e.g. genesycloud_integration_action)
3. A read.... function that looks up a single resource.
4. An update... function that updates a single resource.
5. A delete.... function that deletes a single resource.
Two things to note:
1. All code in these methods should be focused on getting data in and out of Terraform. All code that is used for interacting
with a Genesys API should be encapsulated into a proxy class contained within the package.
2. In general, to keep this file somewhat manageable, if you find yourself with a number of helper functions move them to a
utils function in the package. This will keep the code manageable and easy to work through.
*/
// getAllIntegrationActions retrieves all of the integration action via Terraform in the Genesys Cloud and is used for the exporter
func getAllIntegrationActions(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
iap := getIntegrationActionsProxy(clientConfig)
actions, resp, err := iap.getAllIntegrationActions(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get integration actions %s", err), resp)
}
for _, action := range *actions {
// Don't include "static" actions
if strings.HasPrefix(*action.Id, "static") {
continue
}
resources[*action.Id] = &resourceExporter.ResourceMeta{Name: *action.Name}
}
return resources, nil
}
// createIntegrationAction is used by the integration actions resource to create Genesyscloud integration action
func createIntegrationAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
category := d.Get("category").(string)
integrationId := d.Get("integration_id").(string)
secure := d.Get("secure").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
iap := getIntegrationActionsProxy(sdkConfig)
log.Printf("Creating integration action %s", name)
actionContract, diagErr := BuildSdkActionContract(d)
if diagErr != nil {
return diagErr
}
diagErr = util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
action, resp, err := iap.createIntegrationAction(ctx, &IntegrationAction{
Name: &name,
Category: &category,
IntegrationId: &integrationId,
Secure: &secure,
Contract: actionContract,
Config: BuildSdkActionConfig(d),
})
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create integration action %s error: %s", name, err), resp)
}
d.SetId(*action.Id)
log.Printf("Created integration action %s %s", name, *action.Id)
return resp, nil
})
if diagErr != nil {
return diagErr
}
return readIntegrationAction(ctx, d, meta)
}
// readIntegrationAction is used by the integration action resource to read an action from genesys cloud.
func readIntegrationAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
iap := getIntegrationActionsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIntegrationAction(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading integration action %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
action, resp, err := iap.getIntegrationActionById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration action %s | error: %s", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration action %s | error: %s", d.Id(), err), resp))
}
// Retrieve config request/response templates
reqTemp, resp, err := iap.getIntegrationActionTemplate(ctx, d.Id(), reqTemplateFileName)
if err != nil {
if util.IsStatus404(resp) {
d.SetId("")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read request template for integration action %s | error: %s", d.Id(), err), resp))
}
successTemp, resp, err := iap.getIntegrationActionTemplate(ctx, d.Id(), successTemplateFileName)
if err != nil {
if util.IsStatus404(resp) {
d.SetId("")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read success template for integration action %s | error: %s", d.Id(), err), resp))
}
resourcedata.SetNillableValue(d, "name", action.Name)
resourcedata.SetNillableValue(d, "category", action.Category)
resourcedata.SetNillableValue(d, "integration_id", action.IntegrationId)
resourcedata.SetNillableValue(d, "secure", action.Secure)
resourcedata.SetNillableValue(d, "config_timeout_seconds", action.Config.TimeoutSeconds)
if action.Contract != nil && action.Contract.Input != nil && action.Contract.Input.InputSchema != nil {
input, err := flattenActionContract(*action.Contract.Input.InputSchema)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
d.Set("contract_input", input)
} else {
d.Set("contract_input", nil)
}
if action.Contract != nil && action.Contract.Output != nil && action.Contract.Output.SuccessSchema != nil {
output, err := flattenActionContract(*action.Contract.Output.SuccessSchema)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
d.Set("contract_output", output)
} else {
d.Set("contract_output", nil)
}
if action.Config != nil && action.Config.Request != nil {
action.Config.Request.RequestTemplate = reqTemp
d.Set("config_request", FlattenActionConfigRequest(*action.Config.Request))
} else {
d.Set("config_request", nil)
}
if action.Config != nil && action.Config.Response != nil {
action.Config.Response.SuccessTemplate = successTemp
d.Set("config_response", FlattenActionConfigResponse(*action.Config.Response))
} else {
d.Set("config_response", nil)
}
log.Printf("Read integration action %s %s", d.Id(), *action.Name)
return cc.CheckState(d)
})
}
// updateIntegrationAction is used by the integration action resource to update an action in Genesys Cloud
func updateIntegrationAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
iap := getIntegrationActionsProxy(sdkConfig)
name := d.Get("name").(string)
category := d.Get("category").(string)
log.Printf("Updating integration action %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get the latest action version to send with PATCH
action, resp, err := iap.getIntegrationActionById(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read integration action %s error: %s", d.Id(), err), resp)
}
_, resp, err = iap.updateIntegrationAction(ctx, d.Id(), &platformclientv2.Updateactioninput{
Name: &name,
Category: &category,
Version: action.Version,
Config: BuildSdkActionConfig(d),
})
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update integration action %s error: %s", name, err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated integration action %s", name)
return readIntegrationAction(ctx, d, meta)
}
// deleteIntegrationAction is used by the integration action resource to delete an action from Genesys cloud.
func deleteIntegrationAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
iap := getIntegrationActionsProxy(sdkConfig)
log.Printf("Deleting integration action %s", name)
resp, err := iap.deleteIntegrationAction(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Parent integration was probably deleted which caused the action to be deleted
log.Printf("Integration action already deleted %s", d.Id())
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Integration action %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := iap.getIntegrationActionById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Integration action deleted
log.Printf("Deleted Integration action %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting integration action %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("integration action %s still exists", d.Id()), resp))
})
}
package integration_action
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
)
/*
resource_genesyscloud_integration_action_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the integration_action resource.
3. The datasource schema definitions for the integration_action datasource.
4. The resource exporter configuration for the integration_action exporter.
*/
const resourceName = "genesyscloud_integration_action"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceIntegrationAction())
l.RegisterResource(resourceName, ResourceIntegrationAction())
l.RegisterExporter(resourceName, IntegrationActionExporter())
}
// ResourceIntegrationAction registers the genesyscloud_integration_action resource with Terraform
func ResourceIntegrationAction() *schema.Resource {
actionConfigRequest := &schema.Resource{
Schema: map[string]*schema.Schema{
"request_url_template": {
Description: "URL that may include placeholders for requests to 3rd party service.",
Type: schema.TypeString,
Required: true,
},
"request_type": {
Description: "HTTP method to use for request (GET | PUT | POST | PATCH | DELETE).",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"GET", "PUT", "POST", "PATCH", "DELETE"}, false),
},
"request_template": {
Description: "Velocity template to define request body sent to 3rd party service. Any instances of '${' must be properly escaped as '$${'",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"headers": {
Description: "Map of headers in name, value pairs to include in request.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
actionConfigResponse := &schema.Resource{
Schema: map[string]*schema.Schema{
"translation_map": {
Description: "Map 'attribute name' and 'JSON path' pairs used to extract data from REST response.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"translation_map_defaults": {
Description: "Map 'attribute name' and 'default value' pairs used as fallback values if JSON path extraction fails for specified key.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"success_template": {
Description: "Velocity template to build response to return from Action. Any instances of '${' must be properly escaped as '$${'.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
return &schema.Resource{
Description: "Genesys Cloud Integration Actions. See this page for detailed information on configuring Actions: https://help.mypurecloud.com/articles/add-configuration-custom-actions-integrations/",
CreateContext: provider.CreateWithPooledClient(createIntegrationAction),
ReadContext: provider.ReadWithPooledClient(readIntegrationAction),
UpdateContext: provider.UpdateWithPooledClient(updateIntegrationAction),
DeleteContext: provider.DeleteWithPooledClient(deleteIntegrationAction),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the action. Can be up to 256 characters long",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 256),
},
"category": {
Description: "Category of action. Can be up to 256 characters long.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 256),
},
"integration_id": {
Description: "The ID of the integration this action is associated with. Changing the integration_id attribute will cause the existing integration_action to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"secure": {
Description: "Indication of whether or not the action is designed to accept sensitive data. Changing the secure attribute will cause the existing integration_action to be dropped and recreated with a new ID.",
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"config_timeout_seconds": {
Description: "Optional 1-60 second timeout enforced on the execution or test of this action. This setting is invalid for Custom Authentication Actions.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(1, 60),
},
"contract_input": {
Description: "JSON Schema that defines the body of the request that the client (edge/architect/postman) is sending to the service, on the /execute path. Changing the contract_input attribute will cause the existing integration_action to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"contract_output": {
Description: "JSON schema that defines the transformed, successful result that will be sent back to the caller. Changing the contract_output attribute will cause the existing integration_action to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"config_request": {
Description: "Configuration of outbound request.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: actionConfigRequest,
},
"config_response": {
Description: "Configuration of response processing.",
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: actionConfigResponse,
},
},
}
}
// IntegrationActionExporter returns the resourceExporter object used to hold the genesyscloud_integration_action exporter's config
func IntegrationActionExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIntegrationActions),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"integration_id": {RefType: "genesyscloud_integration"},
},
JsonEncodeAttributes: []string{"contract_input", "contract_output"},
}
}
// DataSourceIntegrationAction registers the genesyscloud_integration_action data source
func DataSourceIntegrationAction() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud integration action. Select an integration action by name",
ReadContext: provider.ReadWithPooledClient(dataSourceIntegrationActionRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the integration action",
Type: schema.TypeString,
Required: true,
},
},
}
}
package integration_action
import (
"encoding/json"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
/*
The resource_genesyscloud_integration_action_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
Note: Look for opportunities to minimize boilerplate code using functions and Generics
*/
const (
reqTemplateFileName = "requesttemplate.vm"
successTemplateFileName = "successtemplate.vm"
)
type ActionInput struct {
InputSchema *interface{} `json:"inputSchema,omitempty"`
}
type ActionOutput struct {
SuccessSchema *interface{} `json:"successSchema,omitempty"`
}
type ActionContract struct {
Output *ActionOutput `json:"output,omitempty"`
Input *ActionInput `json:"input,omitempty"`
}
type IntegrationAction struct {
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Category *string `json:"category,omitempty"`
IntegrationId *string `json:"integrationId,omitempty"`
Secure *bool `json:"secure,omitempty"`
Config *platformclientv2.Actionconfig `json:"config,omitempty"`
Contract *ActionContract `json:"contract,omitempty"`
Version *int `json:"version,omitempty"`
}
// BuildSdkActionContract takes the resource data and builds the custom ActionContract from it
func BuildSdkActionContract(d *schema.ResourceData) (*ActionContract, diag.Diagnostics) {
configInput := d.Get("contract_input").(string)
inputVal, err := util.JsonStringToInterface(configInput)
if err != nil {
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to parse contract input %s", configInput), err)
}
configOutput := d.Get("contract_output").(string)
outputVal, err := util.JsonStringToInterface(configOutput)
if err != nil {
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to parse contract output %s", configInput), err)
}
return &ActionContract{
Input: &ActionInput{InputSchema: &inputVal},
Output: &ActionOutput{SuccessSchema: &outputVal},
}, nil
}
// buildSdkActionConfig takes the resource data and builds the SDK platformclientv2.Actionconfig from it
func BuildSdkActionConfig(d *schema.ResourceData) *platformclientv2.Actionconfig {
ConfigTimeoutSeconds := d.Get("config_timeout_seconds").(int)
ActionConfig := &platformclientv2.Actionconfig{
Request: BuildSdkActionConfigRequest(d),
Response: BuildSdkActionConfigResponse(d),
}
if ConfigTimeoutSeconds > 0 {
ActionConfig.TimeoutSeconds = &ConfigTimeoutSeconds
}
return ActionConfig
}
// buildSdkActionConfigRequest takes the resource data and builds the SDK platformclientv2.Requestconfig from it
func BuildSdkActionConfigRequest(d *schema.ResourceData) *platformclientv2.Requestconfig {
if configRequest := d.Get("config_request"); configRequest != nil {
if configList := configRequest.([]interface{}); len(configList) > 0 {
configMap := configList[0].(map[string]interface{})
urlTemplate := configMap["request_url_template"].(string)
template := configMap["request_template"].(string)
reqType := configMap["request_type"].(string)
headers := map[string]string{}
if headerVal, ok := configMap["headers"]; ok && headerVal != nil {
for key, val := range headerVal.(map[string]interface{}) {
headers[key] = val.(string)
}
}
return &platformclientv2.Requestconfig{
RequestUrlTemplate: &urlTemplate,
RequestTemplate: &template,
RequestType: &reqType,
Headers: &headers,
}
}
}
return &platformclientv2.Requestconfig{}
}
// buildSdkActionConfigResponse takes the resource data and builds the SDK platformclientv2.Responseconfig from it
func BuildSdkActionConfigResponse(d *schema.ResourceData) *platformclientv2.Responseconfig {
if configResponse := d.Get("config_response"); configResponse != nil {
if configList := configResponse.([]interface{}); len(configList) > 0 {
configMap := configList[0].(map[string]interface{})
transMap := map[string]string{}
if mapVal, ok := configMap["translation_map"]; ok && mapVal != nil {
for key, val := range mapVal.(map[string]interface{}) {
transMap[key] = val.(string)
}
}
transMapDefaults := map[string]string{}
if mapVal, ok := configMap["translation_map_defaults"]; ok && mapVal != nil {
for key, val := range mapVal.(map[string]interface{}) {
transMapDefaults[key] = val.(string)
}
}
var successTemplate string
if tempVal, ok := configMap["success_template"]; ok {
successTemplate = tempVal.(string)
}
return &platformclientv2.Responseconfig{
TranslationMap: &transMap,
TranslationMapDefaults: &transMapDefaults,
SuccessTemplate: &successTemplate,
}
}
}
return &platformclientv2.Responseconfig{}
}
// flattenActionContract converts the custom ActionContract into a JSON-encoded string
func flattenActionContract(schema interface{}) (string, diag.Diagnostics) {
if schema == nil {
return "", nil
}
schemaBytes, err := json.Marshal(schema)
if err != nil {
return "", util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error marshalling action contract %v", schema), err)
}
return string(schemaBytes), nil
}
// flattenActionConfigRequest converts the platformclientv2.Requestconfig into a map
func FlattenActionConfigRequest(sdkRequest platformclientv2.Requestconfig) []interface{} {
requestMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(requestMap, "request_url_template", sdkRequest.RequestUrlTemplate)
resourcedata.SetMapValueIfNotNil(requestMap, "request_type", sdkRequest.RequestType)
resourcedata.SetMapValueIfNotNil(requestMap, "request_template", sdkRequest.RequestTemplate)
resourcedata.SetMapValueIfNotNil(requestMap, "headers", sdkRequest.Headers)
return []interface{}{requestMap}
}
// FlattenActionConfigResponse converts the the platformclientv2.Responseconfig into a map
func FlattenActionConfigResponse(sdkResponse platformclientv2.Responseconfig) []interface{} {
responseMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(responseMap, "translation_map", sdkResponse.TranslationMap)
resourcedata.SetMapValueIfNotNil(responseMap, "translation_map_defaults", sdkResponse.TranslationMapDefaults)
resourcedata.SetMapValueIfNotNil(responseMap, "success_template", sdkResponse.SuccessTemplate)
return []interface{}{responseMap}
}
package integration_credential
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_integration_credential.go contains the data source implementation
for the resource.
Note: This code should contain no code for doing the actual lookup in Genesys Cloud. Instead,
it should be added to the _proxy.go file for the class using our proxy pattern.
*/
// dataSourceIntegrationCredentialRead retrieves by name the integration action id in question
func dataSourceIntegrationCredentialRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationCredsProxy(sdkConfig)
credName := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
credential, retryable, resp, err := ip.getIntegrationCredByName(ctx, credName)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get integration credential: %s | error: %s", credential, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no integration credential found: %s", credName), resp))
}
d.SetId(*credential.Id)
return nil
})
}
package integration_credential
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_integration_credential_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *integrationCredsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllIntegrationCredsFunc func(ctx context.Context, p *integrationCredsProxy) (*[]platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error)
type createIntegrationCredFunc func(ctx context.Context, p *integrationCredsProxy, createCredential *platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error)
type getIntegrationCredByIdFunc func(ctx context.Context, p *integrationCredsProxy, credentialId string) (credential *platformclientv2.Credential, response *platformclientv2.APIResponse, err error)
type getIntegrationCredByNameFunc func(ctx context.Context, p *integrationCredsProxy, credentialName string) (credential *platformclientv2.Credentialinfo, retryable bool, response *platformclientv2.APIResponse, err error)
type updateIntegrationCredFunc func(ctx context.Context, p *integrationCredsProxy, credentialId string, credential *platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error)
type deleteIntegrationCredFunc func(ctx context.Context, p *integrationCredsProxy, credentialId string) (response *platformclientv2.APIResponse, err error)
type getIntegrationByIdFunc func(ctx context.Context, p *integrationCredsProxy, integrationId string) (integration *platformclientv2.Integration, response *platformclientv2.APIResponse, err error)
// integrationCredsProxy contains all of the methods that call genesys cloud APIs.
type integrationCredsProxy struct {
clientConfig *platformclientv2.Configuration
integrationsApi *platformclientv2.IntegrationsApi
getAllIntegrationCredsAttr getAllIntegrationCredsFunc
createIntegrationCredAttr createIntegrationCredFunc
getIntegrationCredByIdAttr getIntegrationCredByIdFunc
getIntegrationCredByNameAttr getIntegrationCredByNameFunc
updateIntegrationCredAttr updateIntegrationCredFunc
deleteIntegrationCredAttr deleteIntegrationCredFunc
getIntegrationByIdAttr getIntegrationByIdFunc
}
// newIntegrationCredsProxy initializes the Integration Credentials proxy with all of the data needed to communicate with Genesys Cloud
func newIntegrationCredsProxy(clientConfig *platformclientv2.Configuration) *integrationCredsProxy {
api := platformclientv2.NewIntegrationsApiWithConfig(clientConfig)
return &integrationCredsProxy{
clientConfig: clientConfig,
integrationsApi: api,
getAllIntegrationCredsAttr: getAllIntegrationCredsFn,
createIntegrationCredAttr: createIntegrationCredFn,
getIntegrationCredByIdAttr: getIntegrationCredByIdFn,
getIntegrationCredByNameAttr: getIntegrationCredByNameFn,
updateIntegrationCredAttr: updateIntegrationCredFn,
deleteIntegrationCredAttr: deleteIntegrationCredFn,
getIntegrationByIdAttr: getIntegrationByIdFn,
}
}
// getIntegrationCredsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getIntegrationCredsProxy(clientConfig *platformclientv2.Configuration) *integrationCredsProxy {
if internalProxy == nil {
internalProxy = newIntegrationCredsProxy(clientConfig)
}
return internalProxy
}
// getAllIntegrationCredentials retrieves all Genesys Cloud Integrations
func (p *integrationCredsProxy) getAllIntegrationCreds(ctx context.Context) (*[]platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
return p.getAllIntegrationCredsAttr(ctx, p)
}
// createIntegrationCred creates a Genesys Cloud Crdential
func (p *integrationCredsProxy) createIntegrationCred(ctx context.Context, createCredential *platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
return p.createIntegrationCredAttr(ctx, p, createCredential)
}
// getIntegrationCredById gets a Genesys Cloud Integration Credential by id
func (p *integrationCredsProxy) getIntegrationCredById(ctx context.Context, credentialId string) (credential *platformclientv2.Credential, response *platformclientv2.APIResponse, err error) {
return p.getIntegrationCredByIdAttr(ctx, p, credentialId)
}
// getIntegrationCredByName gets a Genesys Cloud Integration Credential by name
func (p *integrationCredsProxy) getIntegrationCredByName(ctx context.Context, credentialName string) (*platformclientv2.Credentialinfo, bool, *platformclientv2.APIResponse, error) {
return p.getIntegrationCredByNameAttr(ctx, p, credentialName)
}
// updateIntegrationCred udpates a Genesys Cloud Integration Credential
func (p *integrationCredsProxy) updateIntegrationCred(ctx context.Context, credentialId string, credential *platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
return p.updateIntegrationCredAttr(ctx, p, credentialId, credential)
}
// deleteIntegrationCred deletes a Genesys Cloud Integration Credential
func (p *integrationCredsProxy) deleteIntegrationCred(ctx context.Context, credentialId string) (response *platformclientv2.APIResponse, err error) {
return p.deleteIntegrationCredAttr(ctx, p, credentialId)
}
// getIntegrationById gets Genesys Cloud Integration by id
func (p *integrationCredsProxy) getIntegrationById(ctx context.Context, integrationId string) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
return p.getIntegrationByIdAttr(ctx, p, integrationId)
}
// getAllIntegrationCredsFn is the implementation for getting all integration credentials in Genesys Cloud
func getAllIntegrationCredsFn(ctx context.Context, p *integrationCredsProxy) (*[]platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
var allCreds []platformclientv2.Credentialinfo
var resp *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
credentials, response, err := p.integrationsApi.GetIntegrationsCredentials(pageNum, pageSize)
if err != nil {
return nil, response, err
}
resp = response
if credentials.Entities == nil || len(*credentials.Entities) == 0 {
break
}
allCreds = append(allCreds, *credentials.Entities...)
}
return &allCreds, resp, nil
}
// createIntegrationCredFn is the implementation for creating an integration credential in Genesys Cloud
func createIntegrationCredFn(ctx context.Context, p *integrationCredsProxy, createCredential *platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
credential, resp, err := p.integrationsApi.PostIntegrationsCredentials(*createCredential)
if err != nil {
return nil, resp, err
}
return credential, resp, nil
}
// getIntegrationCredByIdFn is the implementation for getting an integration credential by id in Genesys Cloud
func getIntegrationCredByIdFn(ctx context.Context, p *integrationCredsProxy, credentialId string) (*platformclientv2.Credential, *platformclientv2.APIResponse, error) {
credential, resp, err := p.integrationsApi.GetIntegrationsCredential(credentialId)
if err != nil {
return nil, resp, err
}
return credential, resp, nil
}
// getIntegrationCredByNameFn is the implementation for getting an integration credential by name in Genesys Cloud
func getIntegrationCredByNameFn(ctx context.Context, p *integrationCredsProxy, credentialName string) (*platformclientv2.Credentialinfo, bool, *platformclientv2.APIResponse, error) {
var foundCred *platformclientv2.Credentialinfo
var resp *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
integrationCredentials, response, err := p.integrationsApi.GetIntegrationsCredentials(pageNum, pageSize)
resp = response
if err != nil {
return nil, false, response, err
}
if integrationCredentials.Entities == nil || len(*integrationCredentials.Entities) == 0 {
return nil, true, response, fmt.Errorf("no integration credentials found with name: %s", credentialName)
}
for _, credential := range *integrationCredentials.Entities {
if credential.Name != nil && *credential.Name == credentialName {
foundCred = &credential
break
}
}
if foundCred != nil {
break
}
}
return foundCred, false, resp, nil
}
// updateIntegrationCredFn is the implementation for updating an integration credential in Genesys Cloud
func updateIntegrationCredFn(ctx context.Context, p *integrationCredsProxy, credentialId string, credential *platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
credInfo, resp, err := p.integrationsApi.PutIntegrationsCredential(credentialId, *credential)
if err != nil {
return nil, resp, err
}
return credInfo, resp, nil
}
// deleteIntegrationCredFn is the implementation for deleting an integration credential in Genesys Cloud
func deleteIntegrationCredFn(ctx context.Context, p *integrationCredsProxy, credentialId string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.integrationsApi.DeleteIntegrationsCredential(credentialId)
if err != nil {
return resp, err
}
return resp, nil
}
// getIntegrationByIdFn is the implementation for getting a Genesys Cloud Integration by id
func getIntegrationByIdFn(ctx context.Context, p *integrationCredsProxy, integrationId string) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
const pageSize = 100
const pageNum = 1
integration, resp, err := p.integrationsApi.GetIntegration(integrationId, pageSize, pageNum, "", nil, "", "")
if err != nil {
return nil, resp, err
}
return integration, resp, nil
}
package integration_credential
import (
"context"
"fmt"
"log"
"regexp"
"strings"
oauth "terraform-provider-genesyscloud/genesyscloud/oauth_client"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_integration_credential.go contains all of the methods that perform the core logic for a resource.
In general a resource should have a approximately 5 methods in it:
1. A getAll.... function that the CX as Code exporter will use during the process of exporting Genesys Cloud.
2. A create.... function that the resource will use to create a Genesys Cloud object (e.g. genesycloud_integration_credential)
3. A read.... function that looks up a single resource.
4. An update... function that updates a single resource.
5. A delete.... function that deletes a single resource.
Two things to note:
1. All code in these methods should be focused on getting data in and out of Terraform. All code that is used for interacting
with a Genesys API should be encapsulated into a proxy class contained within the package.
2. In general, to keep this file somewhat manageable, if you find yourself with a number of helper functions move them to a
utils function in the package. This will keep the code manageable and easy to work through.
*/
// getAllCredentials retrieves all of the integration credentials via Terraform in the Genesys Cloud and is used for the exporter
func getAllCredentials(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
ip := getIntegrationCredsProxy(clientConfig)
credentials, resp, err := ip.getAllIntegrationCreds(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get all credentials error: %s", err), resp)
}
for _, cred := range *credentials {
log.Printf("Dealing with credential id : %s", *cred.Id)
if cred.Name != nil { // Credential is possible to have no name
// Export integration credential only if it matches the expected format: DEVTOOLING-310
regexPattern := regexp.MustCompile("Integration-.+")
if !regexPattern.MatchString(*cred.Name) {
log.Printf("integration credential name [%s] does not match the expected format [%s], not exporting integration credential id %s", *cred.Name, regexPattern.String(), *cred.Id)
continue
}
// Verify that the integration entity itself exist before exporting the integration credentials associated to it: DEVTOOLING-282
integrationId := strings.Split(*cred.Name, "Integration-")[1]
_, resp, err := ip.getIntegrationById(ctx, integrationId)
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Integration id %s no longer exist, we are therefore not exporting the associated integration credential id %s", integrationId, *cred.Id)
continue
} else {
log.Printf("Integration id %s exists but we got an unexpected error retrieving it: %v", integrationId, err)
}
}
resources[*cred.Id] = &resourceExporter.ResourceMeta{Name: *cred.Name}
}
}
return resources, nil
}
// createCredential is used by the integration credential resource to create Genesyscloud integration credential
func createCredential(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
cred_type := d.Get("credential_type_name").(string)
fields := buildCredentialFields(d)
_, secretFieldPresent := fields["clientSecret"]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationCredsProxy(sdkConfig)
//If if is a Genesys Cloud OAuth Client and the user has not provided a secret field we should look for the
//item in the cache DEVTOOLING-448
if cred_type == "pureCloudOAuthClient" && !secretFieldPresent {
retrieveCachedOauthClientSecret(sdkConfig, fields)
}
createCredential := platformclientv2.Credential{
Name: &name,
VarType: &platformclientv2.Credentialtype{
Name: &cred_type,
},
CredentialFields: &fields,
}
credential, resp, err := ip.createIntegrationCred(ctx, &createCredential)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create credential %s error: %s", name, err), resp)
}
d.SetId(*credential.Id)
log.Printf("Created credential %s, %s", name, *credential.Id)
return readCredential(ctx, d, meta)
}
func retrieveCachedOauthClientSecret(sdkConfig *platformclientv2.Configuration, fields map[string]string) {
op := oauth.GetOAuthClientProxy(sdkConfig)
if clientId, ok := fields["clientId"]; ok {
oAuthClient := op.GetCachedOAuthClient(clientId)
fields["clientSecret"] = *oAuthClient.Secret
log.Printf("Successfully matched with OAuth Client Credential id %s", clientId)
}
}
// readCredential is used by the integration credential resource to read a credential from genesys cloud.
func readCredential(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationCredsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIntegrationCredential(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading credential %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
currentCredential, resp, err := ip.getIntegrationCredById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read credential %s | error: %s", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read credential %s | error: %s", d.Id(), err), resp))
}
_ = d.Set("name", *currentCredential.Name)
_ = d.Set("credential_type_name", *currentCredential.VarType.Name)
log.Printf("Read credential %s %s", d.Id(), *currentCredential.Name)
return cc.CheckState(d)
})
}
// updateCredential is used by the integration credential resource to update a credential in Genesys Cloud
func updateCredential(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
cred_type := d.Get("credential_type_name").(string)
fields := buildCredentialFields(d)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationCredsProxy(sdkConfig)
if d.HasChanges("name", "credential_type_name", "fields") {
log.Printf("Updating credential %s", name)
_, resp, err := ip.updateIntegrationCred(ctx, d.Id(), &platformclientv2.Credential{
Name: &name,
VarType: &platformclientv2.Credentialtype{
Name: &cred_type,
},
CredentialFields: &fields,
})
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update credential %s error: %s", name, err), resp)
}
}
log.Printf("Updated credential %s %s", name, d.Id())
return readCredential(ctx, d, meta)
}
// deleteCredential is used by the integration credential resource to delete a credential from Genesys cloud.
func deleteCredential(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
ip := getIntegrationCredsProxy(sdkConfig)
resp, err := ip.deleteIntegrationCred(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete credential %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := ip.getIntegrationCredById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Integration credential deleted
log.Printf("Deleted Integration credential %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting credential action %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("integration credential %s still exists", d.Id()), resp))
})
}
package integration_credential
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesyscloud_integration_credential_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the integration_credential resource.
3. The datasource schema definitions for the integration_credential datasource.
4. The resource exporter configuration for the integration_credential exporter.
*/
const resourceName = "genesyscloud_integration_credential"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceIntegrationCredential())
l.RegisterResource(resourceName, ResourceIntegrationCredential())
l.RegisterExporter(resourceName, IntegrationCredentialExporter())
}
// ResourceIntegrationCredential registers the genesyscloud_integration_credential resource with Terraform
func ResourceIntegrationCredential() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Credential",
CreateContext: provider.CreateWithPooledClient(createCredential),
ReadContext: provider.ReadWithPooledClient(readCredential),
UpdateContext: provider.UpdateWithPooledClient(updateCredential),
DeleteContext: provider.DeleteWithPooledClient(deleteCredential),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Credential name.",
Type: schema.TypeString,
Optional: true,
},
"credential_type_name": {
Description: "Credential type name. Use [GET /api/v2/integrations/credentials/types](https://developer.genesys.cloud/api/rest/v2/integrations/#get-api-v2-integrations-credentials-types) to see the list of available integration credential types. ",
Type: schema.TypeString,
Required: true,
},
"fields": {
Description: "Credential fields. Different credential types require different fields. Missing any correct required fields will result API request failure. Use [GET /api/v2/integrations/credentials/types](https://developer.genesys.cloud/api/rest/v2/integrations/#get-api-v2-integrations-credentials-types) to check out the specific credential type schema to find out what fields are required. ",
Type: schema.TypeMap,
Optional: true,
Computed: true,
Sensitive: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
// IntegrationCredentialExporter returns the resourceExporter object used to hold the genesyscloud_integration_credential exporter's config
func IntegrationCredentialExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllCredentials),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No Reference
UnResolvableAttributes: map[string]*schema.Schema{
"fields": ResourceIntegrationCredential().Schema["fields"],
},
}
}
// DataSourceIntegrationCredential registers the genesyscloud_integration_credential data source
func DataSourceIntegrationCredential() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud integration credential. Select an integration credential by name",
ReadContext: provider.ReadWithPooledClient(dataSourceIntegrationCredentialRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the integration credential",
Type: schema.TypeString,
Required: true,
},
},
}
}
package integration_credential
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/util"
)
/*
The resource_genesyscloud_integration_credential_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
Note: Look for opportunities to minimize boilerplate code using functions and Generics
*/
// buildCredentialFields builds a map of credential fields from the resource
func buildCredentialFields(d *schema.ResourceData) map[string]string {
results := make(map[string]string)
if fields, ok := d.GetOk("fields"); ok {
fieldMap := fields.(map[string]interface{})
for k, v := range fieldMap {
results[k] = v.(string)
}
return results
}
return results
}
// GenerateCredentialResource generates the terraform string for creating genesyscloud_integration_credential resource. Used for testing.
func GenerateCredentialResource(resourceID string, name string, credentialType string, fields string) string {
return fmt.Sprintf(`resource "genesyscloud_integration_credential" "%s" {
name = %s
credential_type_name = %s
%s
}
`, resourceID, name, credentialType, fields)
}
// GenerateCredentialFields builds a terraform string for multiple credential fields
func GenerateCredentialFields(fields ...string) string {
return util.GenerateMapAttr("fields", fields...)
}
package integration_custom_auth_action
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_integration_custom_auth_action.go contains the data source implementation
for the resource.
Note: This code should contain no code for doing the actual lookup in Genesys Cloud. Instead,
it should be added to the _proxy.go file for the class using our proxy pattern.
*/
// dataSourceIntegrationCustomAuthActionRead retrieves the custom auth action id from the integration name
func dataSourceIntegrationCustomAuthActionRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
cap := getCustomAuthActionsProxy(sdkConfig)
integrationId := d.Get("parent_integration_id").(string)
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
integration, resp, getErr := cap.getIntegrationById(ctx, integrationId)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration %s | error: %s", d.Id(), getErr), resp))
}
// Get the custom auth action for the integration
authActionId := getCustomAuthIdFromIntegration(*integration.Id)
authAction, resp, err := cap.getCustomAuthActionById(ctx, authActionId)
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("cannot find custom auth action of integration %s | error: %v", *integration.Name, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting integration %s | error: %s", d.Id(), err), resp))
}
d.SetId(*authAction.Id)
return nil
})
}
package integration_custom_auth_action
import (
"context"
"fmt"
"strings"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_integration_custom_auth_action_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *customAuthActionsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllIntegrationCustomAuthActionsFunc func(ctx context.Context, p *customAuthActionsProxy) (*[]platformclientv2.Action, *platformclientv2.APIResponse, error)
type getCustomAuthActionByIdFunc func(ctx context.Context, p *customAuthActionsProxy, actionId string) (*platformclientv2.Action, *platformclientv2.APIResponse, error)
type updateCustomAuthActionFunc func(ctx context.Context, p *customAuthActionsProxy, actionId string, updateAction *platformclientv2.Updateactioninput) (*platformclientv2.Action, *platformclientv2.APIResponse, error)
type getIntegrationActionTemplateFunc func(ctx context.Context, p *customAuthActionsProxy, actionId string, fileName string) (*string, *platformclientv2.APIResponse, error)
type getIntegrationTypeFunc func(ctx context.Context, p *customAuthActionsProxy, integrationId string) (string, *platformclientv2.APIResponse, error)
type getIntegrationCredentialsTypeFunc func(ctx context.Context, p *customAuthActionsProxy, integrationId string) (string, *platformclientv2.APIResponse, error)
type getIntegrationByIdFunc func(ctx context.Context, p *customAuthActionsProxy, integrationName string) (integration *platformclientv2.Integration, resp *platformclientv2.APIResponse, err error)
// customAuthActionsProxy contains all of the methods that call genesys cloud APIs.
type customAuthActionsProxy struct {
clientConfig *platformclientv2.Configuration
integrationsApi *platformclientv2.IntegrationsApi
getAllIntegrationCustomAuthActionsAttr getAllIntegrationCustomAuthActionsFunc
getCustomAuthActionByIdAttr getCustomAuthActionByIdFunc
updateCustomAuthActionAttr updateCustomAuthActionFunc
getIntegrationActionTemplateAttr getIntegrationActionTemplateFunc
getIntegrationTypeAttr getIntegrationTypeFunc
getIntegrationCredentialsTypeAttr getIntegrationCredentialsTypeFunc
getIntegrationByIdAttr getIntegrationByIdFunc
}
// newCustomAuthActionsProxy initializes the customAuthActionsProxy with all of the data needed to communicate with Genesys Cloud
func newCustomAuthActionsProxy(clientConfig *platformclientv2.Configuration) *customAuthActionsProxy {
api := platformclientv2.NewIntegrationsApiWithConfig(clientConfig)
return &customAuthActionsProxy{
clientConfig: clientConfig,
integrationsApi: api,
getAllIntegrationCustomAuthActionsAttr: getAllIntegrationCustomAuthActionsFn,
getCustomAuthActionByIdAttr: getCustomAuthActionByIdFn,
updateCustomAuthActionAttr: updateCustomAuthActionFn,
getIntegrationActionTemplateAttr: getIntegrationActionTemplateFn,
getIntegrationTypeAttr: getIntegrationTypeFn,
getIntegrationCredentialsTypeAttr: getIntegrationCredentialsTypeFn,
getIntegrationByIdAttr: getIntegrationByIdFn,
}
}
// getCustomAuthActionsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getCustomAuthActionsProxy(clientConfig *platformclientv2.Configuration) *customAuthActionsProxy {
if internalProxy == nil {
internalProxy = newCustomAuthActionsProxy(clientConfig)
}
return internalProxy
}
// getAllIntegrationCustomAuthActions retrieves all Genesys Cloud Integration Custom Auth Actions
func (p *customAuthActionsProxy) getAllIntegrationCustomAuthActions(ctx context.Context) (*[]platformclientv2.Action, *platformclientv2.APIResponse, error) {
return p.getAllIntegrationCustomAuthActionsAttr(ctx, p)
}
// getCustomAuthActionById retrieve a Genesys Cloud Integration Custom Auth Action by ID
func (p *customAuthActionsProxy) getCustomAuthActionById(ctx context.Context, actionId string) (*platformclientv2.Action, *platformclientv2.APIResponse, error) {
return p.getCustomAuthActionByIdAttr(ctx, p, actionId)
}
// updateCustomAuthAction updates a Genesys Cloud Integration Custom Auth Action
func (p *customAuthActionsProxy) updateCustomAuthAction(ctx context.Context, actionId string, updateAction *platformclientv2.Updateactioninput) (*platformclientv2.Action, *platformclientv2.APIResponse, error) {
return p.updateCustomAuthActionAttr(ctx, p, actionId, updateAction)
}
// getIntegrationActionTemplate retrieves a Genesys Cloud Integration Action Template
func (p *customAuthActionsProxy) getIntegrationActionTemplate(ctx context.Context, actionId string, fileName string) (*string, *platformclientv2.APIResponse, error) {
return p.getIntegrationActionTemplateAttr(ctx, p, actionId, fileName)
}
// getIntegrationType retrieves the type of a Genesys Cloud Integration
func (p *customAuthActionsProxy) getIntegrationType(ctx context.Context, integrationId string) (string, *platformclientv2.APIResponse, error) {
return p.getIntegrationTypeAttr(ctx, p, integrationId)
}
// getIntegrationCredentialsType retrieves the type of a Genesys Cloud Integration Credential
func (p *customAuthActionsProxy) getIntegrationCredentialsType(ctx context.Context, integrationId string) (string, *platformclientv2.APIResponse, error) {
return p.getIntegrationCredentialsTypeAttr(ctx, p, integrationId)
}
// getIntegrationById gets a Genesys Cloud Integration by id
func (p *customAuthActionsProxy) getIntegrationById(ctx context.Context, integrationName string) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
return p.getIntegrationByIdAttr(ctx, p, integrationName)
}
// getAllIntegrationCustomAuthActionsFn is the implementation for getting all integration custom auth actions in Genesys Cloud
func getAllIntegrationCustomAuthActionsFn(ctx context.Context, p *customAuthActionsProxy) (*[]platformclientv2.Action, *platformclientv2.APIResponse, error) {
actions := []platformclientv2.Action{}
const pageSize = 100
actionsList, resp, err := p.integrationsApi.GetIntegrationsActions(pageSize, 1, "", "", "", "", "", "", "", "", "true")
if err != nil {
return nil, resp, err
}
for _, action := range *actionsList.Entities {
if !strings.HasPrefix(*action.Id, customAuthIdPrefix) {
continue
}
actions = append(actions, action)
}
for pageNum := 2; pageNum <= *actionsList.PageCount; pageNum++ {
actionsList, resp, err := p.integrationsApi.GetIntegrationsActions(pageSize, pageNum, "", "", "", "", "", "", "", "", "true")
if err != nil {
return nil, resp, err
}
if actionsList.Entities == nil || len(*actionsList.Entities) == 0 {
break
}
for _, action := range *actionsList.Entities {
if !strings.HasPrefix(*action.Id, customAuthIdPrefix) {
continue
}
actions = append(actions, action)
}
}
return &actions, resp, nil
}
// getCustomAuthActionByIdFn is the implementation for getting an integration custom auth actions by id in Genesys Cloud
func getCustomAuthActionByIdFn(ctx context.Context, p *customAuthActionsProxy, actionId string) (*platformclientv2.Action, *platformclientv2.APIResponse, error) {
action, resp, err := p.integrationsApi.GetIntegrationsAction(actionId, "", true)
if err != nil {
return nil, resp, err
}
return action, resp, nil
}
// updateCustomAuthActionFn is the implementation for updating an integration custom auth action in Genesys Cloud
func updateCustomAuthActionFn(ctx context.Context, p *customAuthActionsProxy, actionId string, updateAction *platformclientv2.Updateactioninput) (*platformclientv2.Action, *platformclientv2.APIResponse, error) {
action, resp, err := p.integrationsApi.PatchIntegrationsAction(actionId, *updateAction)
if err != nil {
return nil, resp, err
}
return action, resp, nil
}
// getIntegrationActionTemplateFn is the implementation for getting the integration action template in Genesys Cloud
func getIntegrationActionTemplateFn(ctx context.Context, p *customAuthActionsProxy, actionId string, fileName string) (*string, *platformclientv2.APIResponse, error) {
template, resp, err := p.integrationsApi.GetIntegrationsActionTemplate(actionId, fileName)
if err != nil {
return nil, resp, err
}
return template, resp, nil
}
// getIntegrationTypeFn is the implementation for getting the type of an integration in Genesys Cloud
func getIntegrationTypeFn(ctx context.Context, p *customAuthActionsProxy, integrationId string) (string, *platformclientv2.APIResponse, error) {
integration, resp, err := p.integrationsApi.GetIntegration(integrationId, 1, 1, "", nil, "", "")
if err != nil {
return "", resp, err
}
return *integration.IntegrationType.Id, resp, nil
}
// getIntegrationCredentialsTypeFn is the implementation for getting the type of an integration credential in Genesys Cloud
func getIntegrationCredentialsTypeFn(ctx context.Context, p *customAuthActionsProxy, integrationId string) (string, *platformclientv2.APIResponse, error) {
integrationConfig, resp, err := p.integrationsApi.GetIntegrationConfigCurrent(integrationId)
if err != nil {
return "", resp, err
}
if integrationConfig.Credentials == nil || len(*integrationConfig.Credentials) == 0 {
return "", resp, fmt.Errorf("no credentials set for integration %s", integrationId)
}
basicAuth, found := (*integrationConfig.Credentials)["basicAuth"]
if !found {
return "", resp, fmt.Errorf("no 'basicAuth' credentials set for integration %s", integrationId)
}
credential, resp, err := p.integrationsApi.GetIntegrationsCredential(*basicAuth.Id)
if err != nil {
return "", resp, err
}
return *credential.VarType.Name, resp, nil
}
// getIntegrationByIdFn is the implementation for getting a Genesys Cloud Integration by id
func getIntegrationByIdFn(ctx context.Context, p *customAuthActionsProxy, integrationId string) (*platformclientv2.Integration, *platformclientv2.APIResponse, error) {
const pageSize = 100
const pageNum = 1
integration, resp, err := p.integrationsApi.GetIntegration(integrationId, pageSize, pageNum, "", nil, "", "")
if err != nil {
return nil, resp, err
}
return integration, resp, nil
}
package integration_custom_auth_action
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
integrationAction "terraform-provider-genesyscloud/genesyscloud/integration_action"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_integration_custom_auth_action.go contains all of the methods that perform the core logic for a resource.
In general a resource should have a approximately 5 methods in it:
1. A getAll.... function that the CX as Code exporter will use during the process of exporting Genesys Cloud.
2. A create.... function that the resource will use to create a Genesys Cloud object (e.g. genesyscloud_integration_custom_auth_action)
3. A read.... function that looks up a single resource.
4. An update... function that updates a single resource.
5. A delete.... function that deletes a single resource.
Two things to note:
1. All code in these methods should be focused on getting data in and out of Terraform. All code that is used for interacting
with a Genesys API should be encapsulated into a proxy class contained within the package.
2. In general, to keep this file somewhat manageable, if you find yourself with a number of helper functions move them to a
utils function in the package. This will keep the code manageable and easy to work through.
*/
// getAllModifiedCustomAuthActions retrieves only the custom auth actions that were modified at least
// once for use in the exporter (version > 1). ie. Unmodified custom auth actions are not to be exported since the defaults
// are created and managed by Genesys itself based on the Integration configuration.
func getAllModifiedCustomAuthActions(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
cap := getCustomAuthActionsProxy(clientConfig)
actions, resp, err := cap.getAllIntegrationCustomAuthActions(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get integration custom auth actions error: %s", err), resp)
}
for _, action := range *actions {
if *action.Version == 1 {
continue
}
resources[*action.Id] = &resourceExporter.ResourceMeta{Name: *action.Name}
}
return resources, nil
}
// createIntegrationCustomAuthAction is used by the custom auth actions resource to manage the Genesyscloud integration custom auth action
func createIntegrationCustomAuthAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
cap := getCustomAuthActionsProxy(sdkConfig)
integrationId := d.Get("integration_id").(string)
authActionId := getCustomAuthIdFromIntegration(integrationId)
name := resourcedata.GetNillableValue[string](d, "name")
// Precheck that integration type and its credential type if it should have a custom auth data action
if ok, err := isIntegrationAndCredTypesCorrect(ctx, cap, integrationId); !ok || err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("configuration of integration %s does not allow for a custom auth data action", integrationId), err)
}
log.Printf("Retrieving the custom auth action of integration %s", integrationId)
// Retrieve the automatically-generated custom auth action
// to make sure it exists before updating
diagErr := util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
authAction, resp, err := cap.getCustomAuthActionById(ctx, authActionId)
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("cannot find custom auth action of integration %s | error: %v", integrationId, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error getting custom auth action %s | error: %s", d.Id(), err), resp))
}
// Get default name if not to be overriden
if name == nil {
name = authAction.Name
}
d.SetId(*authAction.Id)
return nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updating custom auth action of integration %s", integrationId)
// Update the custom auth action with the actual configuration
diagErr = util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get the latest action version to send with PATCH
action, resp, err := cap.getCustomAuthActionById(ctx, authActionId)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read integration custom auth action %s error: %s", authActionId, err), resp)
}
_, resp, err = cap.updateCustomAuthAction(ctx, authActionId, &platformclientv2.Updateactioninput{
Name: name,
Version: action.Version,
Config: BuildSdkCustomAuthActionConfig(d),
})
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update integration action %s error: %s", *name, err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated custom auth action %s", *name)
return readIntegrationCustomAuthAction(ctx, d, meta)
}
// readIntegrationCustomAuthAction is used by the integration action resource to read a custom auth action from genesys cloud
func readIntegrationCustomAuthAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
cap := getCustomAuthActionsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIntegrationCustomAuthAction(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading integration action %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
action, resp, err := cap.getCustomAuthActionById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration custom auth action %s | error: %s", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read integration custom auth action %s | error: %s", d.Id(), err), resp))
}
// Retrieve config request/response templates
reqTemp, resp, err := cap.getIntegrationActionTemplate(ctx, d.Id(), reqTemplateFileName)
if err != nil {
if util.IsStatus404(resp) {
d.SetId("")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read request template for integration action %s | error: %s", d.Id(), err), resp))
}
successTemp, resp, err := cap.getIntegrationActionTemplate(ctx, d.Id(), successTemplateFileName)
if err != nil {
if util.IsStatus404(resp) {
d.SetId("")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read success template for integration action %s | error: %s", d.Id(), err), resp))
}
resourcedata.SetNillableValue(d, "name", action.Name)
resourcedata.SetNillableValue(d, "integration_id", action.IntegrationId)
if action.Config != nil && action.Config.Request != nil {
action.Config.Request.RequestTemplate = reqTemp
d.Set("config_request", integrationAction.FlattenActionConfigRequest(*action.Config.Request))
} else {
d.Set("config_request", nil)
}
if action.Config != nil && action.Config.Response != nil {
action.Config.Response.SuccessTemplate = successTemp
d.Set("config_response", integrationAction.FlattenActionConfigResponse(*action.Config.Response))
} else {
d.Set("config_response", nil)
}
log.Printf("Read integration action %s %s", d.Id(), *action.Name)
return cc.CheckState(d)
})
}
// updateIntegrationCustomAuthAction is used by the integration action resource to update a custom auth in Genesys Cloud
func updateIntegrationCustomAuthAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
cap := getCustomAuthActionsProxy(sdkConfig)
name := resourcedata.GetNillableValue[string](d, "name")
log.Printf("Updating integration custom auth action %s", *name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get the latest action version to send with PATCH
action, resp, err := cap.getCustomAuthActionById(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read integration custom auth action %s error: %s", d.Id(), err), resp)
}
if name == nil {
name = action.Name
}
_, resp, err = cap.updateCustomAuthAction(ctx, d.Id(), &platformclientv2.Updateactioninput{
Name: name,
Version: action.Version,
Config: BuildSdkCustomAuthActionConfig(d),
})
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update integration action %s error: %s", *name, err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated custom auth action %s", *name)
return readIntegrationCustomAuthAction(ctx, d, meta)
}
// deleteIntegrationCustomAuthAction does not do anything as deleting a custom auth action is not possible
func deleteIntegrationCustomAuthAction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
log.Printf("Removing terraform resource integration_custom_auth_action %s will not remove the Data Action itself in the org", name)
log.Printf("The Custom Auth Data Action cannot be removed unless the Web Services Data Action Integration itself is deleted or if the Credentials type is changed from 'User Defined (OAuth)' to a different type")
return nil
}
package integration_custom_auth_action
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesyscloud_integration_custom_auth_action_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the integration_custom_auth_action resource.
3. The datasource schema definitions for the integration_custom_auth_action datasource.
4. The resource exporter configuration for the integration_custom_auth_action exporter.
*/
const resourceName = "genesyscloud_integration_custom_auth_action"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceIntegrationCustomAuthAction())
l.RegisterResource(resourceName, ResourceIntegrationCustomAuthAction())
l.RegisterExporter(resourceName, IntegrationCustomAuthActionExporter())
}
// ResourceIntegrationCustomAuthAction registers the genesyscloud_integration_custom_auth_action resource with Terraform
func ResourceIntegrationCustomAuthAction() *schema.Resource {
actionConfigRequest := &schema.Resource{
Schema: map[string]*schema.Schema{
"request_url_template": {
Description: "URL that may include placeholders for requests to 3rd party service.",
Type: schema.TypeString,
Required: true,
},
"request_type": {
Description: "HTTP method to use for request (GET | PUT | POST | PATCH | DELETE).",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"GET", "PUT", "POST", "PATCH", "DELETE"}, false),
},
"request_template": {
Description: "Velocity template to define request body sent to 3rd party service. Any instances of '${' must be properly escaped as '$${'",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"headers": {
Description: "Map of headers in name, value pairs to include in request.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
actionConfigResponse := &schema.Resource{
Schema: map[string]*schema.Schema{
"translation_map": {
Description: "Map 'attribute name' and 'JSON path' pairs used to extract data from REST response.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"translation_map_defaults": {
Description: "Map 'attribute name' and 'default value' pairs used as fallback values if JSON path extraction fails for specified key.",
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"success_template": {
Description: "Velocity template to build response to return from Action. Any instances of '${' must be properly escaped as '$${'.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
return &schema.Resource{
Description: "Genesys Cloud Integration Actions. See this page for detailed information on configuring Actions: https://help.mypurecloud.com/articles/add-configuration-custom-actions-integrations/",
CreateContext: provider.CreateWithPooledClient(createIntegrationCustomAuthAction),
ReadContext: provider.ReadWithPooledClient(readIntegrationCustomAuthAction),
UpdateContext: provider.UpdateWithPooledClient(updateIntegrationCustomAuthAction),
DeleteContext: provider.DeleteWithPooledClient(deleteIntegrationCustomAuthAction),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"integration_id": {
Description: "The ID of the integration this action is associated with. The integration is required to be of type `custom-rest-actions` and its credentials type set as `userDefinedOAuth`.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Description: "Name of the action to override the default name. Can be up to 256 characters long",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringLenBetween(1, 256),
},
"config_request": {
Description: "Configuration of outbound request.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: actionConfigRequest,
},
"config_response": {
Description: "Configuration of response processing.",
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: actionConfigResponse,
},
},
}
}
// IntegrationCustomAuthActionExporter returns the resourceExporter object used to hold the genesyscloud_integration_custom_auth_action exporter's config
func IntegrationCustomAuthActionExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllModifiedCustomAuthActions),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"integration_id": {RefType: "genesyscloud_integration"},
},
}
}
// DataSourceIntegrationCustomAuthAction registers the genesyscloud_integration_custom_auth_action data source
func DataSourceIntegrationCustomAuthAction() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud integration custom auth action. Select the custom auth action by its associated integration's id.",
ReadContext: provider.ReadWithPooledClient(dataSourceIntegrationCustomAuthActionRead),
Schema: map[string]*schema.Schema{
"parent_integration_id": {
Description: "The id of the integration associated with the custom auth action",
Type: schema.TypeString,
Required: true,
},
},
}
}
package integration_custom_auth_action
import (
"context"
"fmt"
integrationAction "terraform-provider-genesyscloud/genesyscloud/integration_action"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_integration_action_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
Note: Look for opportunities to minimize boilerplate code using functions and Generics
*/
const (
customAuthIdPrefix = "customAuth" // Custom Auth Data Action IDs start with this
customAuthCredentialType = "userDefinedOAuth"
customRestIntegrationType = "custom-rest-actions"
reqTemplateFileName = "requesttemplate.vm"
successTemplateFileName = "successtemplate.vm"
)
// BuildSdkCustomAuthActionConfig takes the resource data and builds the SDK platformclientv2.Actionconfig from it
// This is a stripped version of the integrationAction.BuildSdkActionConfig because 'timeoutSeconds'
// is invalid for Custom Auth Actions
func BuildSdkCustomAuthActionConfig(d *schema.ResourceData) *platformclientv2.Actionconfig {
ActionConfig := &platformclientv2.Actionconfig{
Request: integrationAction.BuildSdkActionConfigRequest(d),
Response: integrationAction.BuildSdkActionConfigResponse(d),
}
return ActionConfig
}
// isIntegrationAndCredTypesCorrect checks if the integration is of type Web Services Data Action ("custom-rest-actions")
// and checks that the credential is configured as "userDefinedOAuth" which are the requirements
// for a Custom Auth Data Action(Genesys Cloud managed) to exist for the integration.
func isIntegrationAndCredTypesCorrect(ctx context.Context, cap *customAuthActionsProxy, integrationId string) (bool, error) {
// Check that the integration is the correct type
integType, resp, err := cap.getIntegrationType(ctx, integrationId)
if err != nil {
return false, fmt.Errorf("cannot identify integration type of integration %s: %v %v", integrationId, err, resp)
}
if integType != customRestIntegrationType {
return false, fmt.Errorf("integration should be of type %v to use custom auth action. Actual: %v", customRestIntegrationType, integType)
}
// Check credentials
credType, resp, err := cap.getIntegrationCredentialsType(ctx, integrationId)
if err != nil {
return false, err
}
if credType != customAuthCredentialType {
return false, fmt.Errorf("credentials type of integration %s should be %s %v", integrationId, customAuthCredentialType, resp)
}
return true, nil
}
// getCustomAuthIdFromIntegration gets the expected custom auth action ID from the integration ID.
func getCustomAuthIdFromIntegration(integrationId string) string {
return fmt.Sprintf("%s_-_%s", customAuthIdPrefix, integrationId)
}
package journey_outcome_predictor
import (
"context"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_journey_outcome_predictor_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *journeyOutcomePredictorProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createJourneyOutcomePredictorFunc func(ctx context.Context, p *journeyOutcomePredictorProxy, outcomePredictor *platformclientv2.Outcomepredictorrequest) (*platformclientv2.Outcomepredictor, *platformclientv2.APIResponse, error)
type getAllJourneyOutcomePredictorFunc func(ctx context.Context, p *journeyOutcomePredictorProxy) (*[]platformclientv2.Outcomepredictor, *platformclientv2.APIResponse, error)
type getJourneyOutcomePredictorByIdFunc func(ctx context.Context, p *journeyOutcomePredictorProxy, id string) (outcomePredictor *platformclientv2.Outcomepredictor, response *platformclientv2.APIResponse, err error)
type deleteJourneyOutcomePredictorFunc func(ctx context.Context, p *journeyOutcomePredictorProxy, id string) (response *platformclientv2.APIResponse, err error)
// journeyOutcomePredictorProxy contains all of the methods that call genesys cloud APIs.
type journeyOutcomePredictorProxy struct {
clientConfig *platformclientv2.Configuration
journeyApi *platformclientv2.JourneyApi
createJourneyOutcomePredictorAttr createJourneyOutcomePredictorFunc
getAllJourneyOutcomePredictorAttr getAllJourneyOutcomePredictorFunc
getJourneyOutcomePredictorByIdAttr getJourneyOutcomePredictorByIdFunc
deleteJourneyOutcomePredictorAttr deleteJourneyOutcomePredictorFunc
}
// newJourneyOutcomePredictorProxy initializes the journey outcome predictor proxy with all of the data needed to communicate with Genesys Cloud
func newJourneyOutcomePredictorProxy(clientConfig *platformclientv2.Configuration) *journeyOutcomePredictorProxy {
journeyApi := platformclientv2.NewJourneyApiWithConfig(clientConfig)
return &journeyOutcomePredictorProxy{
clientConfig: clientConfig,
journeyApi: journeyApi,
createJourneyOutcomePredictorAttr: createJourneyOutcomePredictorFn,
getAllJourneyOutcomePredictorAttr: getAllJourneyOutcomePredictorFn,
getJourneyOutcomePredictorByIdAttr: getJourneyOutcomePredictorByIdFn,
deleteJourneyOutcomePredictorAttr: deleteJourneyOutcomePredictorFn,
}
}
// getJourneyOutcomePredictorProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getJourneyOutcomePredictorProxy(clientConfig *platformclientv2.Configuration) *journeyOutcomePredictorProxy {
if internalProxy == nil {
internalProxy = newJourneyOutcomePredictorProxy(clientConfig)
}
return internalProxy
}
// createJourneyOutcomePredictor creates a Genesys Cloud journey outcome predictor
func (p *journeyOutcomePredictorProxy) createJourneyOutcomePredictor(ctx context.Context, outcomePredictor *platformclientv2.Outcomepredictorrequest) (*platformclientv2.Outcomepredictor, *platformclientv2.APIResponse, error) {
return p.createJourneyOutcomePredictorAttr(ctx, p, outcomePredictor)
}
// getJourneyOutcomePredictor retrieves all Genesys Cloud journey outcome predictor
func (p *journeyOutcomePredictorProxy) getAllJourneyOutcomePredictor(ctx context.Context) (*[]platformclientv2.Outcomepredictor, *platformclientv2.APIResponse, error) {
return p.getAllJourneyOutcomePredictorAttr(ctx, p)
}
// getJourneyOutcomePredictorById returns a single Genesys Cloud journey outcome predictor by Id
func (p *journeyOutcomePredictorProxy) getJourneyOutcomePredictorById(ctx context.Context, predictorId string) (journeyOutcomePredictor *platformclientv2.Outcomepredictor, response *platformclientv2.APIResponse, err error) {
return p.getJourneyOutcomePredictorByIdAttr(ctx, p, predictorId)
}
// deleteJourneyOutcomePredictor deletes a Genesys Cloud journey outcome predictor by Id
func (p *journeyOutcomePredictorProxy) deleteJourneyOutcomePredictor(ctx context.Context, predictorId string) (response *platformclientv2.APIResponse, err error) {
return p.deleteJourneyOutcomePredictorAttr(ctx, p, predictorId)
}
// createJourneyOutcomePredictorFn is an implementation function for creating a Genesys Cloud journey outcome predictor
func createJourneyOutcomePredictorFn(ctx context.Context, p *journeyOutcomePredictorProxy, outcomePredictor *platformclientv2.Outcomepredictorrequest) (*platformclientv2.Outcomepredictor, *platformclientv2.APIResponse, error) {
predictor, resp, err := p.journeyApi.PostJourneyOutcomesPredictors(*outcomePredictor)
if err != nil {
return nil, resp, err
}
return predictor, resp, nil
}
// getAllJourneyOutcomePredictorFn is the implementation for retrieving all journey outcome predictor in Genesys Cloud
func getAllJourneyOutcomePredictorFn(ctx context.Context, p *journeyOutcomePredictorProxy) (*[]platformclientv2.Outcomepredictor, *platformclientv2.APIResponse, error) {
var allPredictors []platformclientv2.Outcomepredictor
predictors, resp, err := p.journeyApi.GetJourneyOutcomesPredictors()
if err != nil {
return nil, resp, err
}
for _, predictor := range *predictors.Entities {
allPredictors = append(allPredictors, predictor)
}
return &allPredictors, resp, nil
}
// getJourneyOutcomePredictorByIdFn is an implementation of the function to get a Genesys Cloud journey outcome predictor by Id
func getJourneyOutcomePredictorByIdFn(ctx context.Context, p *journeyOutcomePredictorProxy, predictorId string) (journeyOutcomePredictor *platformclientv2.Outcomepredictor, response *platformclientv2.APIResponse, err error) {
predictor, resp, err := p.journeyApi.GetJourneyOutcomesPredictor(predictorId)
if err != nil {
return nil, resp, err
}
return predictor, resp, nil
}
// deleteJourneyOutcomePredictorFn is an implementation function for deleting a Genesys Cloud journey outcome predictor
func deleteJourneyOutcomePredictorFn(ctx context.Context, p *journeyOutcomePredictorProxy, predictorId string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.journeyApi.DeleteJourneyOutcomesPredictor(predictorId)
if err != nil {
return resp, err
}
return resp, nil
}
package journey_outcome_predictor
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_journey_outcome_predictor.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthJourneyOutcomePredictor retrieves all of the journey outcome predictor via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthJourneyOutcomePredictors(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
op := getJourneyOutcomePredictorProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
predictors, resp, err := op.getAllJourneyOutcomePredictor(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get predictors: %v", err), resp)
}
for _, predictor := range *predictors {
resources[*predictor.Id] = &resourceExporter.ResourceMeta{Name: *predictor.Id}
}
return resources, nil
}
// createJourneyOutcomePredictor is used by the journey_outcome_predictor resource to create Genesys cloud journey outcome predictor
func createJourneyOutcomePredictor(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
op := getJourneyOutcomePredictorProxy(sdkConfig)
outcomeId := d.Get("outcome_id").(string)
outcome := &platformclientv2.Outcomerefrequest{
Id: &outcomeId,
}
predictorRequest := platformclientv2.Outcomepredictorrequest{
Outcome: outcome,
}
log.Printf("Creating predictor for outcome %s", outcomeId)
predictor, resp, err := op.createJourneyOutcomePredictor(ctx, &predictorRequest)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create predictor: %s | error: %s", outcomeId, err), resp)
}
d.SetId(*predictor.Id)
log.Printf("Created predictor %s", *predictor.Id)
return readJourneyOutcomePredictor(ctx, d, meta)
}
// readJourneyOutcomePredictor is used by the journey_outcome_predictor resource to read an journey outcome predictor from genesys cloud
func readJourneyOutcomePredictor(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
op := getJourneyOutcomePredictorProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceJourneyOutcomePredictor(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading predictor %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
predictor, resp, getErr := op.getJourneyOutcomePredictorById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read predictor %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read predictor %s | error: %s", d.Id(), getErr), resp))
}
d.Set("outcome_id", *predictor.Outcome.Id)
return cc.CheckState(d)
})
}
// deleteJourneyOutcomePredictor is used by the journey_outcome_predictor resource to delete an journey outcome predictor from Genesys cloud
func deleteJourneyOutcomePredictor(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
op := getJourneyOutcomePredictorProxy(sdkConfig)
resp, err := op.deleteJourneyOutcomePredictor(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete predictor %s | error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := op.getJourneyOutcomePredictorById(ctx, d.Id())
if err == nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting predictor %s | error: %s", d.Id(), err), resp))
}
if util.IsStatus404(resp) {
// Success : Predictor deleted
log.Printf("Deleted predictor %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Predictor %s still exists", d.Id()), resp))
})
}
package journey_outcome_predictor
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_journey_outcome_predictor_schema.go holds four functions within it:
1. The registration code that registers the Resource and Exporter for the package.
2. The resource schema definitions for the journey_outcome_predictor resource.
3. The resource exporter configuration for the journey_outcome_predictor exporter.
*/
const resourceName = "genesyscloud_journey_outcome_predictor"
// SetRegistrar registers all of the resources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceJourneyOutcomePredictor())
regInstance.RegisterExporter(resourceName, JourneyOutcomePredictorExporter())
}
// ResourceJourneyOutcomePredictor registers the genesyscloud_journey_outcome_predictor resource with Terraform
func ResourceJourneyOutcomePredictor() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud journey outcome predictor`,
CreateContext: provider.CreateWithPooledClient(createJourneyOutcomePredictor),
ReadContext: provider.ReadWithPooledClient(readJourneyOutcomePredictor),
DeleteContext: provider.DeleteWithPooledClient(deleteJourneyOutcomePredictor),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"outcome_id": {
Description: "The outcome associated with this predictor",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
// JourneyOutcomePredictorExporter returns the resourceExporter object used to hold the genesyscloud_journey_outcome_predictor exporter's config
func JourneyOutcomePredictorExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthJourneyOutcomePredictors),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"outcome_id": {RefType: "genesyscloud_journey_outcome"},
},
}
}
package oauth_client
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceOAuthClientRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
oauthClientProxy := GetOAuthClientProxy(sdkConfig)
name := d.Get("name").(string)
// Find first non-deleted oauth client by name. Retry in case new oauth client is not yet indexed by search
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
clients, resp, getErr := oauthClientProxy.getAllOAuthClients(ctx)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting oauth client %s | error: %s", name, getErr), resp))
}
if len(*clients) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No oauth clients found with name %s", name), resp))
}
for _, oauth := range *clients {
if oauth.Name != nil && *oauth.Name == name &&
oauth.State != nil && *oauth.State != "deleted" {
d.SetId(*oauth.Id)
return nil
}
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Unable to locate a oauth client with name %s", name), resp))
})
}
package oauth_client
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllOAuthClients(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
oauthClientProxy := GetOAuthClientProxy(clientConfig)
clients, resp, getErr := oauthClientProxy.getAllOAuthClients(ctx)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of oauth clients error: %s", getErr), resp)
}
for _, client := range *clients {
if client.State != nil && *client.State == "disabled" {
// Don't include clients disabled by support
continue
}
resources[*client.Id] = &resourceExporter.ResourceMeta{Name: *client.Name}
}
return resources, nil
}
func createOAuthClient(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
tokenSeconds := d.Get("access_token_validity_seconds").(int)
grantType := d.Get("authorized_grant_type").(string)
state := d.Get("state").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
oauthClientProxy := GetOAuthClientProxy(sdkConfig)
roles, diagErr := buildOAuthRoles(d)
if diagErr != nil {
return diagErr
}
//Before we create the oauth client we need to take any roles that are assigned to this oauth client and assign them to the oauth client running this script
diagErr = updateTerraformUserWithRole(ctx, sdkConfig, roles)
if diagErr != nil {
return diagErr
}
log.Printf("Creating oauth client %s", name)
oauthRequest := &platformclientv2.Oauthclientrequest{
Name: &name,
Description: &description,
AccessTokenValiditySeconds: &tokenSeconds,
AuthorizedGrantType: &grantType,
State: &state,
RegisteredRedirectUri: buildOAuthRedirectURIs(d),
Scope: buildOAuthScopes(d),
RoleDivisions: roles,
}
client, resp, err := oauthClientProxy.createOAuthClient(ctx, *oauthRequest)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create oauth client %s error: %s", name, err), resp)
}
credentialName := resourcedata.GetNillableValue[string](d, "integration_credential_name")
if credentialName != nil {
cred_type := "pureCloudOAuthClient"
results := make(map[string]string)
results["clientId"] = *client.Id
results["clientSecret"] = *client.Secret
createCredential := platformclientv2.Credential{
Name: credentialName,
VarType: &platformclientv2.Credentialtype{
Name: &cred_type,
},
CredentialFields: &results,
}
credential, resp, err := oauthClientProxy.createIntegrationClient(ctx, createCredential)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create credential %s error: %s", name, err), resp)
}
_ = d.Set("integration_credential_id", *credential.Id)
_ = d.Set("integration_credential_name", *credential.Name)
}
d.SetId(*client.Id)
log.Printf("Created oauth client %s %s", name, *client.Id)
return readOAuthClient(ctx, d, meta)
}
func updateTerraformUserWithRole(ctx context.Context, sdkConfig *platformclientv2.Configuration, addedRoles *[]platformclientv2.Roledivision) diag.Diagnostics {
op := GetOAuthClientProxy(sdkConfig)
//Step #1 Retrieve the parent oauth client from the token API and check to make sure it is not a client credential grant
tokenInfo, resp, err := op.getParentOAuthClientToken(ctx)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Error trying to retrieve the token for the OAuth client running our CX as Code provider %s", err), resp)
}
if *tokenInfo.OAuthClient.Organization.Id != "purecloud-builtin" {
log.Printf("This terraform client is being run with an OAuth Client Credential Grant. You might get an error in your terraform scripts if you try to create a role in CX as Code and try to assign it to the oauth client.")
return nil
}
//Step #2: Look up the user who is running the user
log.Printf("The OAuth Client being used is purecloud-builtin. Retrieving the user running the terraform client and assigning the target role to them.")
terraformUser, resp, err := op.GetTerraformUser(ctx)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to retrieved the terraform user running this terraform code %s", err), resp)
}
//Step #3: Lookup the users addedRoles
userRoles, resp, err := op.GetTerraformUserRoles(ctx, *terraformUser.Id)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to retrieve the terraform user addedRoles running this terraform code %s", err), resp)
}
var totalRoles []string
//Step #4 - Concat the addedRoles
for _, role := range *addedRoles {
totalRoles = append(totalRoles, *role.RoleId)
}
for _, role := range *userRoles.Roles {
totalRoles = append(totalRoles, *role.Id)
}
//Step #5 - Update addedRoles
_, resp, err = op.UpdateTerraformUserRoles(ctx, *terraformUser.Id, totalRoles)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update the terraform user addedRoles running this terraform code %s", err), resp)
}
//Do not remove this sleep. The auth service is a mishmash of caches and eventually consistency. After we perform an update we need
//to sleep approximately 10 seconds for the item to be written across multiple databases. Originally, I tried to do a retry loop to
//wait until the retry happens but the act of the first call immediately happen could cause bad data to cache. After talking with the auth
//team we put a sleep in here.
time.Sleep(10 * time.Second)
return nil
}
func readOAuthClient(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
oAuthProxy := GetOAuthClientProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOAuthClient(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading oauth client %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
client, resp, getErr := oAuthProxy.getOAuthClient(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read oauth client %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read oauth client %s | error: %s", d.Id(), getErr), resp))
}
_ = d.Set("name", *client.Name)
resourcedata.SetNillableValue(d, "description", client.Description)
resourcedata.SetNillableValue(d, "access_token_validity_seconds", client.AccessTokenValiditySeconds)
resourcedata.SetNillableValue(d, "authorized_grant_type", client.AuthorizedGrantType)
resourcedata.SetNillableValue(d, "state", client.State)
if client.RegisteredRedirectUri != nil {
_ = d.Set("registered_redirect_uris", lists.StringListToSet(*client.RegisteredRedirectUri))
} else {
_ = d.Set("registered_redirect_uris", nil)
}
if client.Scope != nil {
_ = d.Set("scopes", lists.StringListToSet(*client.Scope))
} else {
_ = d.Set("scopes", nil)
}
if client.RoleDivisions != nil {
_ = d.Set("roles", flattenOAuthRoles(*client.RoleDivisions))
} else {
_ = d.Set("roles", nil)
}
log.Printf("Read oauth client %s %s", d.Id(), *client.Name)
return cc.CheckState(d)
})
}
func updateOAuthClient(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
tokenSeconds := d.Get("access_token_validity_seconds").(int)
grantType := d.Get("authorized_grant_type").(string)
state := d.Get("state").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
oauthClientProxy := GetOAuthClientProxy(sdkConfig)
roles, diagErr := buildOAuthRoles(d)
if diagErr != nil {
return diagErr
}
log.Printf("Updating oauth client %s", name)
_, resp, err := oauthClientProxy.updateOAuthClient(ctx, d.Id(), platformclientv2.Oauthclientrequest{
Name: &name,
Description: &description,
AccessTokenValiditySeconds: &tokenSeconds,
AuthorizedGrantType: &grantType,
State: &state,
RegisteredRedirectUri: buildOAuthRedirectURIs(d),
Scope: buildOAuthScopes(d),
RoleDivisions: roles,
})
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update oauth client %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated oauth client %s", name)
return readOAuthClient(ctx, d, meta)
}
func deleteOAuthClient(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
oauthClientProxy := GetOAuthClientProxy(sdkConfig)
// check if there is a integration credential to delete
credentialId := resourcedata.GetNillableValue[string](d, "integration_credential_id")
if credentialId != nil {
currentCredential, resp, getErr := oauthClientProxy.getIntegrationCredential(ctx, d.Id())
if getErr == nil {
_, err := oauthClientProxy.deleteIntegrationCredential(ctx, d.Id())
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete integration credential %s %s", *currentCredential.Name, err), resp)
}
}
name := d.Get("name").(string)
log.Printf("Deleting oauth client %s", name)
// The client state must be set to inactive before deleting
_ = d.Set("state", "inactive")
diagErr := updateOAuthClient(ctx, d, meta)
if diagErr != nil {
return diagErr
}
resp, err := oauthClientProxy.deleteOAuthClient(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete oauth client %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
oauthClient, resp, err := oauthClientProxy.getOAuthClient(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// OAuth client deleted
log.Printf("Deleted OAuth client %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting OAuth client %s | error: %s", d.Id(), err), resp))
}
if oauthClient.State != nil && *oauthClient.State == "deleted" {
// OAuth client deleted
log.Printf("Deleted OAuth client %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("OAuth client %s still exists", d.Id()), resp))
})
}
package oauth_client
import (
"context"
"log"
"sync"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *oauthClientProxy
type createOAuthClientFunc func(context.Context, *oauthClientProxy, platformclientv2.Oauthclientrequest) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error)
type createIntegrationClientFunc func(context.Context, *oauthClientProxy, platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error)
type updateOAuthClientFunc func(context.Context, *oauthClientProxy, string, platformclientv2.Oauthclientrequest) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error)
type getOAuthClientFunc func(context.Context, *oauthClientProxy, string) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error)
type getParentOAuthClientTokenFunc func(context.Context, *oauthClientProxy) (*platformclientv2.Tokeninfo, *platformclientv2.APIResponse, error)
type getTerraformUserFunc func(context.Context, *oauthClientProxy) (*platformclientv2.Userme, *platformclientv2.APIResponse, error)
type getTerraformUserRolesFunc func(context.Context, *oauthClientProxy, string) (*platformclientv2.Userauthorization, *platformclientv2.APIResponse, error)
type updateTerraformUserRolesFunc func(context.Context, *oauthClientProxy, string, []string) (*platformclientv2.Userauthorization, *platformclientv2.APIResponse, error)
type getIntegrationCredentialFunc func(context.Context, *oauthClientProxy, string) (*platformclientv2.Credential, *platformclientv2.APIResponse, error)
type getAllOauthClientsFunc func(ctx context.Context, o *oauthClientProxy) (*[]platformclientv2.Oauthclientlisting, *platformclientv2.APIResponse, error)
type deleteOAuthClientFunc func(context.Context, *oauthClientProxy, string) (*platformclientv2.APIResponse, error)
type deleteIntegrationCredentialFunc func(context.Context, *oauthClientProxy, string) (*platformclientv2.APIResponse, error)
type oauthClientProxy struct {
clientConfig *platformclientv2.Configuration
oAuthApi *platformclientv2.OAuthApi
integrationApi *platformclientv2.IntegrationsApi
tokenApi *platformclientv2.TokensApi
usersApi *platformclientv2.UsersApi
createdClientCache map[string]platformclientv2.Oauthclient //Being added for DEVTOOLING-448
createdClientCacheLock sync.Mutex
createOAuthClientAttr createOAuthClientFunc
createIntegrationCredentialAttr createIntegrationClientFunc
getOAuthClientAttr getOAuthClientFunc
getParentOAuthClientTokenAttr getParentOAuthClientTokenFunc
getTerraformUserAttr getTerraformUserFunc
getTerraformUserRolesAttr getTerraformUserRolesFunc
updateTerraformUserRolesAttr updateTerraformUserRolesFunc
getAllOauthClientsAttr getAllOauthClientsFunc
getIntegrationCredentialAttr getIntegrationCredentialFunc
updateOAuthClientAttr updateOAuthClientFunc
deleteOAuthClientAttr deleteOAuthClientFunc
deleteIntegrationCredentialAttr deleteIntegrationCredentialFunc
}
// newAuthClientProxy initializes the proxy with all the data needed to communicate with Genesys Cloud
func newOAuthClientProxy(clientConfig *platformclientv2.Configuration) *oauthClientProxy {
oAuthApi := platformclientv2.NewOAuthApiWithConfig(clientConfig)
intApi := platformclientv2.NewIntegrationsApiWithConfig(clientConfig)
usersApi := platformclientv2.NewUsersApiWithConfig(clientConfig)
createdClientCache := make(map[string]platformclientv2.Oauthclient)
tokenApi := platformclientv2.NewTokensApiWithConfig(clientConfig)
return &oauthClientProxy{
clientConfig: clientConfig,
oAuthApi: oAuthApi,
integrationApi: intApi,
usersApi: usersApi,
tokenApi: tokenApi,
createdClientCache: createdClientCache,
createOAuthClientAttr: createOAuthClientFn,
createIntegrationCredentialAttr: createIntegrationCredentialFn,
updateOAuthClientAttr: updateOAuthClientFn,
getOAuthClientAttr: getOAuthClientFn,
getParentOAuthClientTokenAttr: getParentOAuthClientTokenFn,
getTerraformUserRolesAttr: getTerraformUserRolesFn,
getTerraformUserAttr: getTerraformUserFn,
updateTerraformUserRolesAttr: updateTerraformUserRolesFn,
getIntegrationCredentialAttr: getIntegrationClientFn,
getAllOauthClientsAttr: getAllOauthClientsFn,
deleteOAuthClientAttr: deleteOAuthClientFn,
deleteIntegrationCredentialAttr: deleteIntegrationClientFn,
}
}
/*
Note: Normally we do not make proxies or their methods public outside the package. However, we are doing this
specifically for DEVTOOLING-448. In DEVTOOLING-448, we are adding the ability to cache a OAuthClient that was
created in a Terraform run so that when can use that secret to create a Genesys Cloud Integration Credential in the same
run without having to expose the secret.
We need this so that we can support the ability run CX as Code Accelerator where we can create a OAuth Client, a Role with Permissions
and then an OAuth client with out the need for the user to support passing the Genesys Cloud OAuth Client Credentials
into the integration credential object. Today the integration credential object has no way of looking up the client id/client secret
without because once the oauth client is created, we dont want to expose the secret.
*/
func GetOAuthClientProxy(clientConfig *platformclientv2.Configuration) *oauthClientProxy {
if internalProxy == nil {
internalProxy = newOAuthClientProxy(clientConfig)
}
return internalProxy
}
func (o *oauthClientProxy) deleteOAuthClient(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return o.deleteOAuthClientAttr(ctx, o, id)
}
func (o *oauthClientProxy) deleteIntegrationCredential(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return o.deleteIntegrationCredentialAttr(ctx, o, id)
}
func (o *oauthClientProxy) getOAuthClient(ctx context.Context, id string) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error) {
return o.getOAuthClientAttr(ctx, o, id)
}
func (o *oauthClientProxy) getParentOAuthClientToken(ctx context.Context) (*platformclientv2.Tokeninfo, *platformclientv2.APIResponse, error) {
return o.getParentOAuthClientTokenAttr(ctx, o)
}
func (o *oauthClientProxy) GetTerraformUser(ctx context.Context) (*platformclientv2.Userme, *platformclientv2.APIResponse, error) {
return o.getTerraformUserAttr(ctx, o)
}
func (o *oauthClientProxy) GetTerraformUserRoles(ctx context.Context, userId string) (*platformclientv2.Userauthorization, *platformclientv2.APIResponse, error) {
return o.getTerraformUserRolesAttr(ctx, o, userId)
}
func (o *oauthClientProxy) UpdateTerraformUserRoles(ctx context.Context, userId string, roles []string) (*platformclientv2.Userauthorization, *platformclientv2.APIResponse, error) {
return o.updateTerraformUserRolesAttr(ctx, o, userId, roles)
}
func (o *oauthClientProxy) getIntegrationCredential(ctx context.Context, id string) (*platformclientv2.Credential, *platformclientv2.APIResponse, error) {
return o.getIntegrationCredentialAttr(ctx, o, id)
}
func (o *oauthClientProxy) GetCachedOAuthClient(clientId string) platformclientv2.Oauthclient {
o.createdClientCacheLock.Lock()
defer o.createdClientCacheLock.Unlock()
return o.createdClientCache[clientId]
}
func (o *oauthClientProxy) createOAuthClient(ctx context.Context, oauthClient platformclientv2.Oauthclientrequest) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error) {
oauthClientResult, response, err := o.createOAuthClientAttr(ctx, o, oauthClient)
if err != nil {
return oauthClientResult, response, err
}
//Being added for DEVTOOLING-448. This is one of the few places where we want to use a cache outside the export
o.createdClientCacheLock.Lock()
defer o.createdClientCacheLock.Unlock()
o.createdClientCache[*oauthClientResult.Id] = *oauthClientResult
log.Printf("Successfully added oauth client %s to cache", *oauthClientResult.Id)
return oauthClientResult, response, err
}
func (o *oauthClientProxy) createIntegrationClient(ctx context.Context, credential platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
return o.createIntegrationCredentialAttr(ctx, o, credential)
}
func (o *oauthClientProxy) updateOAuthClient(ctx context.Context, id string, client platformclientv2.Oauthclientrequest) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error) {
return o.updateOAuthClientAttr(ctx, o, id, client)
}
func (o *oauthClientProxy) getAllOAuthClients(ctx context.Context) (*[]platformclientv2.Oauthclientlisting, *platformclientv2.APIResponse, error) {
return o.getAllOauthClientsAttr(ctx, o)
}
func (o *oauthClientProxy) getHomeDivisionInfo(ctx context.Context) (*platformclientv2.Authzdivision, *platformclientv2.APIResponse, error) {
return o.getHomeDivisionInfo(ctx)
}
func getOAuthClientFn(ctx context.Context, o *oauthClientProxy, id string) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error) {
return o.oAuthApi.GetOauthClient(id)
}
func getAllOauthClientsFn(ctx context.Context, o *oauthClientProxy) (*[]platformclientv2.Oauthclientlisting, *platformclientv2.APIResponse, error) {
var clients []platformclientv2.Oauthclientlisting
firstPage, resp, err := o.oAuthApi.GetOauthClients()
if err != nil {
return nil, resp, err
}
clients = append(clients, *firstPage.Entities...)
for pageNum := 2; pageNum <= *firstPage.PageCount; pageNum++ {
page, resp, err := o.oAuthApi.GetOauthClients()
if err != nil {
return nil, resp, err
}
clients = append(clients, *page.Entities...)
}
return &clients, resp, nil
}
func getIntegrationClientFn(ctx context.Context, o *oauthClientProxy, id string) (*platformclientv2.Credential, *platformclientv2.APIResponse, error) {
return o.integrationApi.GetIntegrationsCredential(id)
}
func deleteOAuthClientFn(ctx context.Context, o *oauthClientProxy, id string) (*platformclientv2.APIResponse, error) {
return o.oAuthApi.DeleteOauthClient(id)
}
func deleteIntegrationClientFn(ctx context.Context, o *oauthClientProxy, id string) (*platformclientv2.APIResponse, error) {
return o.integrationApi.DeleteIntegrationsCredential(id)
}
func createOAuthClientFn(ctx context.Context, o *oauthClientProxy, request platformclientv2.Oauthclientrequest) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error) {
return o.oAuthApi.PostOauthClients(request)
}
func createIntegrationCredentialFn(ctx context.Context, o *oauthClientProxy, request platformclientv2.Credential) (*platformclientv2.Credentialinfo, *platformclientv2.APIResponse, error) {
return o.integrationApi.PostIntegrationsCredentials(request)
}
func updateOAuthClientFn(ctx context.Context, o *oauthClientProxy, id string, request platformclientv2.Oauthclientrequest) (*platformclientv2.Oauthclient, *platformclientv2.APIResponse, error) {
return o.oAuthApi.PutOauthClient(id, request)
}
func getParentOAuthClientTokenFn(ctx context.Context, o *oauthClientProxy) (*platformclientv2.Tokeninfo, *platformclientv2.APIResponse, error) {
return o.tokenApi.GetTokensMe(false)
}
func getTerraformUserFn(ctx context.Context, o *oauthClientProxy) (*platformclientv2.Userme, *platformclientv2.APIResponse, error) {
return o.usersApi.GetUsersMe(nil, "")
}
func getTerraformUserRolesFn(ctx context.Context, o *oauthClientProxy, userId string) (*platformclientv2.Userauthorization, *platformclientv2.APIResponse, error) {
return o.usersApi.GetUserRoles(userId)
}
func updateTerraformUserRolesFn(ctx context.Context, o *oauthClientProxy, userId string, roles []string) (*platformclientv2.Userauthorization, *platformclientv2.APIResponse, error) {
return o.usersApi.PutUserRoles(userId, roles)
}
package oauth_client
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const (
resourceName = "genesyscloud_oauth_client"
)
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceOAuthClient())
l.RegisterResource(resourceName, ResourceOAuthClient())
l.RegisterExporter(resourceName, OauthClientExporter())
}
var (
oauthClientRoleDivResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"role_id": {
Description: "Role to be associated with the given division which forms a grant.",
Type: schema.TypeString,
Required: true,
},
"division_id": {
Description: "Division associated with the given role which forms a grant. If not set, the home division will be used. '*' may be set for all divisions.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
)
func ResourceOAuthClient() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud OAuth Clients. See this page for detailed configuration information: https://help.mypurecloud.com/articles/create-an-oauth-client/",
CreateContext: provider.CreateWithPooledClient(createOAuthClient),
ReadContext: provider.ReadWithPooledClient(readOAuthClient),
UpdateContext: provider.UpdateWithPooledClient(updateOAuthClient),
DeleteContext: provider.DeleteWithPooledClient(deleteOAuthClient),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the OAuth client.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "The description of the OAuth client.",
Type: schema.TypeString,
Optional: true,
},
"access_token_validity_seconds": {
Description: "The number of seconds, between 5mins and 48hrs, until tokens created with this client expire. Only clients using Genesys Cloud SCIM (Identity Management) can have a maximum duration of 38880000secs/450 days.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(300, 38880000),
Default: 86400,
},
"registered_redirect_uris": {
Description: "List of allowed callbacks for this client. For example: https://myapp.example.com/auth/callback.",
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"authorized_grant_type": {
Description: "The OAuth Grant/Client type supported by this client (CODE | TOKEN | SAML2BEARER | PASSWORD | CLIENT-CREDENTIALS).",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"CODE", "TOKEN", "SAML2BEARER", "PASSWORD", "CLIENT-CREDENTIALS"}, false),
},
"scopes": {
Description: "The scopes requested by this client. Scopes must be set for clients not using the CLIENT-CREDENTIALS grant.",
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"roles": {
Description: "Set of roles and their corresponding divisions associated with this client. Roles must be set for clients using the CLIENT-CREDENTIALS grant. The roles must also already be assigned to the OAuth Client used by Terraform.",
Type: schema.TypeSet,
Elem: oauthClientRoleDivResource,
Optional: true,
},
"state": {
Description: "The state of the OAuth client (active | inactive). Access tokens cannot be created with inactive clients.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"active", "inactive"}, false),
Default: "active",
},
"integration_credential_id": {
Description: "The Id of the created Integration Credential using this new OAuth Client.",
Type: schema.TypeString,
Optional: false,
Required: false,
Computed: true, //If Required and Optional are both false, the attribute will be considered
// "read only" for the practitioner, with only the provider able to set its value.
},
"integration_credential_name": {
Description: "Optionally, a Name of a Integration Credential (with credential type pureCloudOAuthClient) to be created using this new OAuth Client.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
func OauthClientExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllOAuthClients),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"roles.role_id": {RefType: "genesyscloud_auth_role"},
"roles.division_id": {RefType: "genesyscloud_auth_division", AltValues: []string{"*"}},
"integration_credential_id": {RefType: "genesyscloud_integration_credential"},
},
RemoveIfMissing: map[string][]string{
"roles": {"role_id"},
"integration_credential_id": {"integration_credential_id"},
"integration_credential_name": {"integration_credential_name"},
},
}
}
func DataSourceOAuthClient() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud OAuth Clients. Select an OAuth Client by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOAuthClientRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "OAuth Client name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package oauth_client
import (
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func buildOAuthRedirectURIs(d *schema.ResourceData) *[]string {
if config, ok := d.GetOk("registered_redirect_uris"); ok {
return lists.SetToStringList(config.(*schema.Set))
}
return nil
}
func buildOAuthScopes(d *schema.ResourceData) *[]string {
if config, ok := d.GetOk("scopes"); ok {
return lists.SetToStringList(config.(*schema.Set))
}
return nil
}
func buildOAuthRoles(d *schema.ResourceData) (*[]platformclientv2.Roledivision, diag.Diagnostics) {
if config, ok := d.GetOk("roles"); ok {
var sdkRoles []platformclientv2.Roledivision
roleConfig := config.(*schema.Set).List()
for _, role := range roleConfig {
roleMap := role.(map[string]interface{})
roleId := roleMap["role_id"].(string)
var divisionId string
if divConfig, ok := roleMap["division_id"]; ok {
divisionId = divConfig.(string)
}
if divisionId == "" {
// Set to home division if not set
var diagErr diag.Diagnostics
divisionId, diagErr = util.GetHomeDivisionID()
if diagErr != nil {
return nil, diagErr
}
}
roleDiv := platformclientv2.Roledivision{
RoleId: &roleId,
DivisionId: &divisionId,
}
sdkRoles = append(sdkRoles, roleDiv)
}
return &sdkRoles, nil
}
return nil, nil
}
func flattenOAuthRoles(sdkRoles []platformclientv2.Roledivision) *schema.Set {
roleSet := schema.NewSet(schema.HashResource(oauthClientRoleDivResource), []interface{}{})
for _, roleDiv := range sdkRoles {
role := make(map[string]interface{})
if roleDiv.RoleId != nil {
role["role_id"] = *roleDiv.RoleId
}
if roleDiv.DivisionId != nil {
role["division_id"] = *roleDiv.DivisionId
}
roleSet.Add(role)
}
return roleSet
}
package organization_authentication_settings
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_organization_authentication_settings_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *orgAuthSettingsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getOrgAuthSettingsByIdFunc func(ctx context.Context, p *orgAuthSettingsProxy, id string) (orgAuthSettings *platformclientv2.Orgauthsettings, response *platformclientv2.APIResponse, err error)
type updateOrgAuthSettingsFunc func(ctx context.Context, p *orgAuthSettingsProxy, orgAuthSettings *platformclientv2.Orgauthsettings) (*platformclientv2.Orgauthsettings, *platformclientv2.APIResponse, error)
// orgAuthSettingsProxy contains all of the methods that call genesys cloud APIs.
type orgAuthSettingsProxy struct {
clientConfig *platformclientv2.Configuration
organizationApi *platformclientv2.OrganizationApi
getOrgAuthSettingsByIdAttr getOrgAuthSettingsByIdFunc
updateOrgAuthSettingsAttr updateOrgAuthSettingsFunc
}
// newOrgAuthSettingsProxy initializes the organization authentication settings proxy with all of the data needed to communicate with Genesys Cloud
func newOrgAuthSettingsProxy(clientConfig *platformclientv2.Configuration) *orgAuthSettingsProxy {
api := platformclientv2.NewOrganizationApiWithConfig(clientConfig)
return &orgAuthSettingsProxy{
clientConfig: clientConfig,
organizationApi: api,
getOrgAuthSettingsByIdAttr: getOrgAuthSettingsByIdFn,
updateOrgAuthSettingsAttr: updateOrgAuthSettingsFn,
}
}
// getOrgAuthSettingsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOrgAuthSettingsProxy(clientConfig *platformclientv2.Configuration) *orgAuthSettingsProxy {
if internalProxy == nil {
internalProxy = newOrgAuthSettingsProxy(clientConfig)
}
return internalProxy
}
// getOrgAuthSettingsById returns a single Genesys Cloud organization authentication settings by Id
func (p *orgAuthSettingsProxy) getOrgAuthSettingsById(ctx context.Context, id string) (orgAuthSettings *platformclientv2.Orgauthsettings, response *platformclientv2.APIResponse, err error) {
return p.getOrgAuthSettingsByIdAttr(ctx, p, id)
}
// updateOrgAuthSettings updates a Genesys Cloud organization authentication settings
func (p *orgAuthSettingsProxy) updateOrgAuthSettings(ctx context.Context, orgAuthSettings *platformclientv2.Orgauthsettings) (*platformclientv2.Orgauthsettings, *platformclientv2.APIResponse, error) {
return p.updateOrgAuthSettingsAttr(ctx, p, orgAuthSettings)
}
// getOrgAuthSettingsByIdFn is an implementation of the function to get a Genesys Cloud organization authentication settings by Id
func getOrgAuthSettingsByIdFn(ctx context.Context, p *orgAuthSettingsProxy, id string) (orgAuthSettings *platformclientv2.Orgauthsettings, response *platformclientv2.APIResponse, err error) {
orgAuthSettings, resp, err := p.organizationApi.GetOrganizationsAuthenticationSettings()
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve organization authentication settings by id %s: %s", id, err)
}
return orgAuthSettings, resp, nil
}
// updateOrgAuthSettingsFn is an implementation of the function to update a Genesys Cloud organization authentication settings
func updateOrgAuthSettingsFn(ctx context.Context, p *orgAuthSettingsProxy, orgAuthSettings *platformclientv2.Orgauthsettings) (*platformclientv2.Orgauthsettings, *platformclientv2.APIResponse, error) {
authSettings, resp, err := p.organizationApi.PatchOrganizationsAuthenticationSettings(*orgAuthSettings)
if err != nil {
return nil, resp, fmt.Errorf("failed to update organization authentication settings: %s", err)
}
return authSettings, resp, nil
}
package organization_authentication_settings
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_organization_authentication_settings.go contains all the methods that perform the core logic for a resource.
*/
func getAllOrganizationAuthenticationSettings(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
resources["0"] = &resourceExporter.ResourceMeta{Name: "organization_authentication_settings"}
return resources, nil
}
// createOrganizationAuthenticationSettings is used by the organization_authentication_settings resource to create Genesys cloud organization authentication settings
func createOrganizationAuthenticationSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating Organization Authentication Settings")
d.SetId("Settings")
return updateOrganizationAuthenticationSettings(ctx, d, meta)
}
// readOrganizationAuthenticationSettings is used by the organization_authentication_settings resource to read an organization authentication settings from genesys cloud
func readOrganizationAuthenticationSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOrgAuthSettingsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOrganizationAuthenticationSettings(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading organization authentication settings %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
orgAuthSettings, resp, getErr := proxy.getOrgAuthSettingsById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read organization authentication settings %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read organization authentication settings %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "multifactor_authentication_required", orgAuthSettings.MultifactorAuthenticationRequired)
resourcedata.SetNillableValue(d, "domain_allowlist_enabled", orgAuthSettings.DomainAllowlistEnabled)
resourcedata.SetNillableValue(d, "domain_allowlist", orgAuthSettings.DomainAllowlist)
resourcedata.SetNillableValue(d, "ip_address_allowlist", orgAuthSettings.IpAddressAllowlist)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "password_requirements", orgAuthSettings.PasswordRequirements, flattenPasswordRequirements)
log.Printf("Read organization authentication settings %s", d.Id())
return cc.CheckState(d)
})
}
// updateOrganizationAuthenticationSettings is used by the organization_authentication_settings resource to update an organization authentication settings in Genesys Cloud
func updateOrganizationAuthenticationSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOrgAuthSettingsProxy(sdkConfig)
authSettings := getOrganizationAuthenticationSettingsFromResourceData(d)
log.Printf("Updating organization authentication settings %s", d.Id())
orgAuthSettings, resp, err := proxy.updateOrgAuthSettings(ctx, &authSettings)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update organization authentication settings: %s", err), resp)
}
log.Printf("Updated organization authentication settings %s %s", d.Id(), orgAuthSettings)
return readOrganizationAuthenticationSettings(ctx, d, meta)
}
// deleteOrganizationAuthenticationSettings is used by the organization_authentication_settings resource to delete an organization authentication settings from Genesys cloud
func deleteOrganizationAuthenticationSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}
package organization_authentication_settings
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_organization_authentication_settings_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the organization_authentication_settings resource.
3. The datasource schema definitions for the organization_authentication_settings datasource.
4. The resource exporter configuration for the organization_authentication_settings exporter.
*/
const resourceName = "genesyscloud_organization_authentication_settings"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterResource(resourceName, ResourceOrganizationAuthenticationSettings())
l.RegisterExporter(resourceName, OrganizationAuthenticationSettingsExporter())
}
var passwordRequirements = &schema.Resource{
Schema: map[string]*schema.Schema{
`minimum_length`: {
Description: "The minimum character length for passwords",
Optional: true,
Type: schema.TypeInt,
},
`minimum_digits`: {
Description: "The minimum number of numerals (0-9) that must be included in passwords",
Optional: true,
Type: schema.TypeInt,
},
`minimum_letters`: {
Description: "The minimum number of characters required for passwords",
Optional: true,
Type: schema.TypeInt,
},
`minimum_upper`: {
Description: "The minimum number of upper case letters that must be included in passwords",
Optional: true,
Type: schema.TypeInt,
},
`minimum_lower`: {
Description: "The minimum number of lower case letters that must be included in passwords",
Optional: true,
Type: schema.TypeInt,
},
`minimum_specials`: {
Description: "The minimum number of special characters that must be included in passwords",
Optional: true,
Type: schema.TypeInt,
},
`minimum_age_seconds`: {
Description: "Minimum age of the password (in seconds) before it can be changed",
Optional: true,
Type: schema.TypeInt,
},
`expiration_days`: {
Description: "Length of time (in days) before a password must be changed",
Optional: true,
Type: schema.TypeInt,
},
},
}
// ResourceOrganizationAuthenticationSettings registers the genesyscloud_organization_authentication_settings resource with Terraform
func ResourceOrganizationAuthenticationSettings() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud organization authentication settings`,
CreateContext: provider.CreateWithPooledClient(createOrganizationAuthenticationSettings),
ReadContext: provider.ReadWithPooledClient(readOrganizationAuthenticationSettings),
UpdateContext: provider.UpdateWithPooledClient(updateOrganizationAuthenticationSettings),
DeleteContext: provider.DeleteWithPooledClient(deleteOrganizationAuthenticationSettings),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`multifactor_authentication_required`: {
Description: `Indicates whether multi-factor authentication is required.`,
Optional: true,
Type: schema.TypeBool,
},
`domain_allowlist_enabled`: {
Description: `Indicates whether the domain allowlist is enabled.`,
Optional: true,
Type: schema.TypeBool,
},
`domain_allowlist`: {
Description: `The list of domains that will be allowed to embed Genesys Cloud applications.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`ip_address_allowlist`: {
Description: `The list of IP addresses that will be allowed to authenticate with Genesys Cloud. Warning: Changing these will result in only allowing specified ip Addresses to log in and will invalidate credentials with a different ip address`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`password_requirements`: {
Description: `The password requirements for the organization.`,
Optional: true,
Type: schema.TypeList,
MaxItems: 1,
Elem: passwordRequirements,
},
},
}
}
func OrganizationAuthenticationSettingsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllOrganizationAuthenticationSettings),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{},
}
}
package organization_authentication_settings
import (
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_organization_authentication_settings_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
// getOrganizationAuthenticationSettingsFromResourceData maps data from schema ResourceData object to a platformclientv2.Orgauthsettings
func getOrganizationAuthenticationSettingsFromResourceData(d *schema.ResourceData) platformclientv2.Orgauthsettings {
return platformclientv2.Orgauthsettings{
PasswordRequirements: buildPasswordRequirements(d, "password_requirements"),
MultifactorAuthenticationRequired: platformclientv2.Bool(d.Get("multifactor_authentication_required").(bool)),
DomainAllowlistEnabled: platformclientv2.Bool(d.Get("domain_allowlist_enabled").(bool)),
DomainAllowlist: lists.BuildSdkStringListFromInterfaceArray(d, "domain_allowlist"),
IpAddressAllowlist: lists.BuildSdkStringListFromInterfaceArray(d, "ip_address_allowlist"),
}
}
// buildPasswordRequirements maps an []interface{} into a Genesys Cloud *[]platformclientv2.Passwordrequirements
func buildPasswordRequirements(d *schema.ResourceData, key string) *platformclientv2.Passwordrequirements {
if d.Get(key) != nil {
passwordData := d.Get(key).([]interface{})
if len(passwordData) > 0 {
pReqMap := passwordData[0].(map[string]interface{})
minimumLength := pReqMap["minimum_length"].(int)
minimumDigits := pReqMap["minimum_digits"].(int)
minimumLetters := pReqMap["minimum_letters"].(int)
minimumUpper := pReqMap["minimum_upper"].(int)
minimumLower := pReqMap["minimum_lower"].(int)
minimumSpecials := pReqMap["minimum_specials"].(int)
minimumAgeSeconds := pReqMap["minimum_age_seconds"].(int)
expirationDays := pReqMap["expiration_days"].(int)
return &platformclientv2.Passwordrequirements{
MinimumLength: &minimumLength,
MinimumDigits: &minimumDigits,
MinimumLetters: &minimumLetters,
MinimumUpper: &minimumUpper,
MinimumLower: &minimumLower,
MinimumSpecials: &minimumSpecials,
MinimumAgeSeconds: &minimumAgeSeconds,
ExpirationDays: &expirationDays,
}
}
}
return nil
}
// flattenPasswordRequirements maps a Genesys Cloud []platformclientv2.Passwordrequirements into a []interface{}
func flattenPasswordRequirements(passwordRequirements *platformclientv2.Passwordrequirements) []interface{} {
pReqInterface := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(pReqInterface, "minimum_length", passwordRequirements.MinimumLength)
resourcedata.SetMapValueIfNotNil(pReqInterface, "minimum_digits", passwordRequirements.MinimumDigits)
resourcedata.SetMapValueIfNotNil(pReqInterface, "minimum_letters", passwordRequirements.MinimumLetters)
resourcedata.SetMapValueIfNotNil(pReqInterface, "minimum_upper", passwordRequirements.MinimumUpper)
resourcedata.SetMapValueIfNotNil(pReqInterface, "minimum_lower", passwordRequirements.MinimumLower)
resourcedata.SetMapValueIfNotNil(pReqInterface, "minimum_specials", passwordRequirements.MinimumSpecials)
resourcedata.SetMapValueIfNotNil(pReqInterface, "minimum_age_seconds", passwordRequirements.MinimumAgeSeconds)
resourcedata.SetMapValueIfNotNil(pReqInterface, "expiration_days", passwordRequirements.ExpirationDays)
return []interface{}{pReqInterface}
}
package orgauthorization_pairing
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *orgauthorizationPairingProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOrgauthorizationPairingFunc func(ctx context.Context, p *orgauthorizationPairingProxy, trustRequestCreate *platformclientv2.Trustrequestcreate) (*platformclientv2.Trustrequest, *platformclientv2.APIResponse, error)
type getOrgauthorizationPairingByIdFunc func(ctx context.Context, p *orgauthorizationPairingProxy, id string) (trustRequest *platformclientv2.Trustrequest, response *platformclientv2.APIResponse, err error)
// orgauthorizationPairingProxy contains all the methods that call genesys cloud APIs.
type orgauthorizationPairingProxy struct {
clientConfig *platformclientv2.Configuration
organizationAuthorizationApi *platformclientv2.OrganizationAuthorizationApi
createOrgauthorizationPairingAttr createOrgauthorizationPairingFunc
getOrgauthorizationPairingByIdAttr getOrgauthorizationPairingByIdFunc
}
// newOrgauthorizationPairingProxy initializes the orgauthorization pairing proxy with all of the data needed to communicate with Genesys Cloud
func newOrgauthorizationPairingProxy(clientConfig *platformclientv2.Configuration) *orgauthorizationPairingProxy {
api := platformclientv2.NewOrganizationAuthorizationApiWithConfig(clientConfig)
return &orgauthorizationPairingProxy{
clientConfig: clientConfig,
organizationAuthorizationApi: api,
createOrgauthorizationPairingAttr: createOrgauthorizationPairingFn,
getOrgauthorizationPairingByIdAttr: getOrgauthorizationPairingByIdFn,
}
}
// getOrgauthorizationPairingProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOrgauthorizationPairingProxy(clientConfig *platformclientv2.Configuration) *orgauthorizationPairingProxy {
if internalProxy == nil {
internalProxy = newOrgauthorizationPairingProxy(clientConfig)
}
return internalProxy
}
// createOrgauthorizationPairing creates a Genesys Cloud orgauthorization pairing
func (p *orgauthorizationPairingProxy) createOrgauthorizationPairing(ctx context.Context, orgauthorizationPairing *platformclientv2.Trustrequestcreate) (*platformclientv2.Trustrequest, *platformclientv2.APIResponse, error) {
return p.createOrgauthorizationPairingAttr(ctx, p, orgauthorizationPairing)
}
// getOrgauthorizationPairingById returns a single Genesys Cloud orgauthorization pairing by Id
func (p *orgauthorizationPairingProxy) getOrgauthorizationPairingById(ctx context.Context, id string) (orgauthorizationPairing *platformclientv2.Trustrequest, response *platformclientv2.APIResponse, err error) {
return p.getOrgauthorizationPairingByIdAttr(ctx, p, id)
}
// createOrgauthorizationPairingFn is an implementation function for creating a Genesys Cloud orgauthorization pairing
func createOrgauthorizationPairingFn(ctx context.Context, p *orgauthorizationPairingProxy, orgauthorizationPairing *platformclientv2.Trustrequestcreate) (*platformclientv2.Trustrequest, *platformclientv2.APIResponse, error) {
trustRequestCreate, resp, err := p.organizationAuthorizationApi.PostOrgauthorizationPairings(*orgauthorizationPairing)
if err != nil {
return nil, resp, fmt.Errorf("failed to create orgauthorization pairing: %s", err)
}
return trustRequestCreate, resp, nil
}
// getOrgauthorizationPairingByIdFn is an implementation of the function to get a Genesys Cloud orgauthorization pairing by Id
func getOrgauthorizationPairingByIdFn(ctx context.Context, p *orgauthorizationPairingProxy, id string) (orgauthorizationPairing *platformclientv2.Trustrequest, response *platformclientv2.APIResponse, err error) {
trustRequestCreate, resp, err := p.organizationAuthorizationApi.GetOrgauthorizationPairing(id)
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve orgauthorization pairing by id %s: %s", id, err)
}
return trustRequestCreate, resp, nil
}
package orgauthorization_pairing
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func createOrgauthorizationPairing(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOrgauthorizationPairingProxy(sdkConfig)
userIds := lists.InterfaceListToStrings(d.Get("user_ids").([]interface{}))
groupIds := lists.InterfaceListToStrings(d.Get("group_ids").([]interface{}))
trustRequestCreate := platformclientv2.Trustrequestcreate{
UserIds: &userIds,
GroupIds: &groupIds,
}
log.Printf("Creating Orgauthorization Pairing")
pairing, resp, err := proxy.createOrgauthorizationPairing(ctx, &trustRequestCreate)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Orgauthorization Pairing | error: %s", err), resp)
}
d.SetId(*pairing.Id)
log.Printf("Created Orgauthorization Pairing %s", *pairing.Id)
return readOrgauthorizationPairing(ctx, d, meta)
}
func readOrgauthorizationPairing(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOrgauthorizationPairingProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOrgauthorizationPairing(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Orgauthorization Pairing %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
trustRequest, resp, getErr := proxy.getOrgauthorizationPairingById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Orgauthorization Pairing %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Orgauthorization Pairing %s | error: %s", d.Id(), getErr), resp))
}
schemaUserIds := lists.InterfaceListToStrings(d.Get("user_ids").([]interface{}))
if trustRequest.Users != nil {
ids := make([]string, 0)
for _, item := range *trustRequest.Users {
ids = append(ids, *item.Id)
}
// if lists are the same: Set in original order to avoid plan not empty error
if lists.AreEquivalent(schemaUserIds, ids) {
d.Set("user_ids", schemaUserIds)
} else {
d.Set("user_ids", ids)
}
}
schemaGroupIds := lists.InterfaceListToStrings(d.Get("group_ids").([]interface{}))
if trustRequest.Groups != nil {
ids := make([]string, 0)
for _, item := range *trustRequest.Groups {
ids = append(ids, *item.Id)
}
// if lists are the same: Set in original order to avoid plan not empty error
if lists.AreEquivalent(schemaGroupIds, ids) {
d.Set("group_ids", schemaGroupIds)
} else {
d.Set("group_ids", ids)
}
}
log.Printf("Read Orgauthorization Pairing %s", d.Id())
return cc.CheckState(d)
})
}
func deleteOrgauthorizationPairing(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}
package orgauthorization_pairing
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_orgauthorization_pairing"
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceOrgauthorizationPairing())
}
func ResourceOrgauthorizationPairing() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud orgauthorization pairing`,
CreateContext: provider.CreateWithPooledClient(createOrgauthorizationPairing),
ReadContext: provider.ReadWithPooledClient(readOrgauthorizationPairing),
DeleteContext: provider.DeleteWithPooledClient(deleteOrgauthorizationPairing),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`user_ids`: {
Description: `The list of trustee users that are requesting access. If no users are specified, at least one group is required. Changing the user_ids attribute will cause the orgauthorization_pairing resource to be dropped and recreated with a new ID.`,
Optional: true,
ForceNew: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`group_ids`: {
Description: `The list of trustee groups that are requesting access. If no groups are specified, at least one user is required. Changing the group_ids attribute will cause the orgauthorization_pairing resource to be dropped and recreated with a new ID.`,
Optional: true,
ForceNew: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
package outbound
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceOutboundAttemptLimit() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Outbound Attempt Limits. Select an attempt limit by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundAttemptLimitRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Attempt Limit name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceOutboundAttemptLimitRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
outboundAPI := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageNum = 1
const pageSize = 100
attemptLimits, resp, getErr := outboundAPI.GetOutboundAttemptlimits(pageSize, pageNum, true, "", name, "", "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting attempt limit %s | error: %s", name, getErr), resp))
}
if attemptLimits.Entities == nil || len(*attemptLimits.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no attempt limits found with name %s", name), resp))
}
attemptLimit := (*attemptLimits.Entities)[0]
d.SetId(*attemptLimit.Id)
return nil
})
}
package outbound
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceOutboundMessagingcampaign() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Outbound Messaging Campaign. Select a Outbound Messaging Campaign by name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundMessagingcampaignRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: `Outbound Messaging Campaign name.`,
Type: schema.TypeString,
Optional: true,
},
},
}
}
func dataSourceOutboundMessagingcampaignRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
sdkMessagingcampaignEntityListing, resp, getErr := outboundApi.GetOutboundMessagingcampaigns(pageSize, pageNum, "", "", "", "", []string{}, "", "", []string{})
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting Outbound Messaging Campaign %s | error: %s", name, getErr), resp))
}
if sdkMessagingcampaignEntityListing.Entities == nil || len(*sdkMessagingcampaignEntityListing.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no Outbound Messaging Campaign found with name %s", name), resp))
}
for _, entity := range *sdkMessagingcampaignEntityListing.Entities {
if entity.Name != nil && *entity.Name == name {
d.SetId(*entity.Id)
return nil
}
}
}
})
}
package outbound
import (
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource("genesyscloud_outbound_attempt_limit", DataSourceOutboundAttemptLimit())
l.RegisterDataSource("genesyscloud_outbound_messagingcampaign", dataSourceOutboundMessagingcampaign())
l.RegisterResource("genesyscloud_outbound_messagingcampaign", ResourceOutboundMessagingCampaign())
l.RegisterExporter("genesyscloud_outbound_messagingcampaign", OutboundMessagingcampaignExporter())
}
package outbound
import (
"context"
"errors"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
const (
resourceName = "genesyscloud_outbound_messagingcampaign"
)
var (
OutboundmessagingcampaigncontactsortResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`field_name`: {
Description: `The field name by which to sort contacts.`,
Required: true,
Type: schema.TypeString,
},
`direction`: {
Description: `The direction in which to sort contacts.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`ASC`, `DESC`}, false),
Default: `ASC`,
},
`numeric`: {
Description: `Whether or not the column contains numeric data.`,
Optional: true,
Type: schema.TypeBool,
Default: false,
},
},
}
outboundmessagingcampaignsmsconfigResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`message_column`: {
Description: `The Contact List column specifying the message to send to the contact. Either message_column or content_template_id is required.`,
Optional: true,
Type: schema.TypeString,
},
`phone_column`: {
Description: `The Contact List column specifying the phone number to send a message to.`,
Required: true,
Type: schema.TypeString,
},
`sender_sms_phone_number`: {
Description: `A phone number provisioned for SMS communications in E.164 format. E.g. +13175555555 or +34234234234`,
Required: true,
Type: schema.TypeString,
},
`content_template_id`: {
Description: `The content template used to formulate the message to send to the contact. Either message_column or content_template_id is required.`,
Optional: true,
Type: schema.TypeString,
},
},
}
)
func ResourceOutboundMessagingCampaign() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Outbound Messaging Campaign`,
CreateContext: provider.CreateWithPooledClient(createOutboundMessagingcampaign),
ReadContext: provider.ReadWithPooledClient(readOutboundMessagingcampaign),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundMessagingcampaign),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundMessagingcampaign),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The campaign name.`,
Required: true,
Type: schema.TypeString,
},
`division_id`: {
Description: `The division this entity belongs to.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`callable_time_set_id`: {
Description: `The callable time set for this messaging campaign.`,
Optional: true,
Type: schema.TypeString,
},
`contact_list_id`: {
Description: `The contact list that this messaging campaign will send messages for.`,
Required: true,
Type: schema.TypeString,
},
`dnc_list_ids`: {
Description: `The dnc lists to check before sending a message for this messaging campaign.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`campaign_status`: {
Description: `The current status of the messaging campaign. A messaging campaign may be turned 'on' or 'off'.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`on`, `off`}, false),
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if old == `complete` && new == `on` {
return true
}
if old == `invalid` && new == `on` {
return true
}
if old == `stopping` && new == `off` {
return true
}
return false
},
},
`always_running`: {
Description: `Whether this messaging campaign is always running`,
Optional: true,
Default: false,
Type: schema.TypeBool,
},
`contact_sorts`: {
Description: `The order in which to sort contacts for dialing, based on up to four columns.`,
Optional: true,
Type: schema.TypeList,
Elem: OutboundmessagingcampaigncontactsortResource,
},
`messages_per_minute`: {
Description: `How many messages this messaging campaign will send per minute.`,
Required: true,
Type: schema.TypeInt,
},
`contact_list_filter_ids`: {
Description: `The contact list filter to check before sending a message for this messaging campaign.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`sms_config`: {
Description: `Configuration for this messaging campaign to send SMS messages.`,
Required: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: outboundmessagingcampaignsmsconfigResource,
},
},
}
}
func getAllOutboundMessagingcampaign(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
outboundApi := platformclientv2.NewOutboundApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
sdkMessagingcampaignEntityListing, resp, getErr := outboundApi.GetOutboundMessagingcampaigns(pageSize, pageNum, "", "", "", "", []string{}, "", "", []string{})
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Error requesting page of Outbound Messagingcampaign error: %s", getErr), resp)
}
if sdkMessagingcampaignEntityListing.Entities == nil || len(*sdkMessagingcampaignEntityListing.Entities) == 0 {
break
}
for _, entity := range *sdkMessagingcampaignEntityListing.Entities {
resources[*entity.Id] = &resourceExporter.ResourceMeta{Name: *entity.Name}
}
}
return resources, nil
}
func OutboundMessagingcampaignExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundMessagingcampaign),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
`division_id`: {RefType: "genesyscloud_auth_division"},
`contact_list_id`: {RefType: "genesyscloud_outbound_contact_list"},
`contact_list_filter_ids`: {RefType: "genesyscloud_outbound_contactlistfilter"},
`dnc_list_ids`: {RefType: "genesyscloud_outbound_dnclist"},
`callable_time_set_id`: {RefType: "genesyscloud_outbound_callabletimeset"},
// /api/v2/responsemanagement/responses/{responseId}
`sms_config.content_template_id`: {},
},
}
}
func createOutboundMessagingcampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
alwaysRunning := d.Get("always_running").(bool)
messagesPerMinute := d.Get("messages_per_minute").(int)
campaignStatus := d.Get("campaign_status").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
sdkmessagingcampaign := platformclientv2.Messagingcampaign{
Division: util.BuildSdkDomainEntityRef(d, "division_id"),
CallableTimeSet: util.BuildSdkDomainEntityRef(d, "callable_time_set_id"),
ContactList: util.BuildSdkDomainEntityRef(d, "contact_list_id"),
DncLists: util.BuildSdkDomainEntityRefArr(d, "dnc_list_ids"),
AlwaysRunning: &alwaysRunning,
ContactSorts: buildSdkoutboundmessagingcampaignContactsortSlice(d.Get("contact_sorts").([]interface{})),
MessagesPerMinute: &messagesPerMinute,
ContactListFilters: util.BuildSdkDomainEntityRefArr(d, "contact_list_filter_ids"),
SmsConfig: buildSdkoutboundmessagingcampaignSmsconfig(d.Get("sms_config").(*schema.Set)),
}
if name != "" {
sdkmessagingcampaign.Name = &name
}
if campaignStatus != "" {
sdkmessagingcampaign.CampaignStatus = &campaignStatus
}
msg, valid := validateSmsconfig(d.Get("sms_config").(*schema.Set))
if !valid {
return util.BuildDiagnosticError(resourceName, "Configuration error", errors.New(msg))
}
log.Printf("Creating Outbound Messagingcampaign %s", name)
outboundMessagingcampaign, resp, err := outboundApi.PostOutboundMessagingcampaigns(sdkmessagingcampaign)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create outbound messagingcampaign %s error: %s", name, err), resp)
}
d.SetId(*outboundMessagingcampaign.Id)
log.Printf("Created Outbound Messagingcampaign %s %s", name, *outboundMessagingcampaign.Id)
return readOutboundMessagingcampaign(ctx, d, meta)
}
func updateOutboundMessagingcampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
alwaysRunning := d.Get("always_running").(bool)
messagesPerMinute := d.Get("messages_per_minute").(int)
campaignStatus := d.Get("campaign_status").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
sdkmessagingcampaign := platformclientv2.Messagingcampaign{
Division: util.BuildSdkDomainEntityRef(d, "division_id"),
CallableTimeSet: util.BuildSdkDomainEntityRef(d, "callable_time_set_id"),
ContactList: util.BuildSdkDomainEntityRef(d, "contact_list_id"),
DncLists: util.BuildSdkDomainEntityRefArr(d, "dnc_list_ids"),
AlwaysRunning: &alwaysRunning,
ContactSorts: buildSdkoutboundmessagingcampaignContactsortSlice(d.Get("contact_sorts").([]interface{})),
MessagesPerMinute: &messagesPerMinute,
ContactListFilters: util.BuildSdkDomainEntityRefArr(d, "contact_list_filter_ids"),
SmsConfig: buildSdkoutboundmessagingcampaignSmsconfig(d.Get("sms_config").(*schema.Set)),
}
if name != "" {
sdkmessagingcampaign.Name = &name
}
if campaignStatus != "" {
sdkmessagingcampaign.CampaignStatus = &campaignStatus
}
msg, valid := validateSmsconfig(d.Get("sms_config").(*schema.Set))
if !valid {
return util.BuildDiagnosticError(resourceName, "Configuration error", errors.New(msg))
}
log.Printf("Updating Outbound Messagingcampaign %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current Outbound Messagingcampaign version
outboundMessagingcampaign, resp, getErr := outboundApi.GetOutboundMessagingcampaign(d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Messagingcampaign %s error: %s", name, getErr), resp)
}
sdkmessagingcampaign.Version = outboundMessagingcampaign.Version
outboundMessagingcampaign, resp, updateErr := outboundApi.PutOutboundMessagingcampaign(d.Id(), sdkmessagingcampaign)
if updateErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound Messagingcampaign %s error: %s", name, updateErr), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Outbound Messagingcampaign %s", name)
return readOutboundMessagingcampaign(ctx, d, meta)
}
func readOutboundMessagingcampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundMessagingCampaign(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Messagingcampaign %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkmessagingcampaign, resp, getErr := outboundApi.GetOutboundMessagingcampaign(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Messagingcampaign %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Messagingcampaign %s | error: %s", d.Id(), getErr), resp))
}
if sdkmessagingcampaign.Name != nil {
d.Set("name", *sdkmessagingcampaign.Name)
}
if sdkmessagingcampaign.Division != nil && sdkmessagingcampaign.Division.Id != nil {
d.Set("division_id", *sdkmessagingcampaign.Division.Id)
}
if sdkmessagingcampaign.CallableTimeSet != nil && sdkmessagingcampaign.CallableTimeSet.Id != nil {
d.Set("callable_time_set_id", *sdkmessagingcampaign.CallableTimeSet.Id)
}
if sdkmessagingcampaign.ContactList != nil && sdkmessagingcampaign.ContactList.Id != nil {
d.Set("contact_list_id", *sdkmessagingcampaign.ContactList.Id)
}
if sdkmessagingcampaign.CampaignStatus != nil {
d.Set("campaign_status", *sdkmessagingcampaign.CampaignStatus)
}
if sdkmessagingcampaign.DncLists != nil {
var dncListIds []string
for _, dnc := range *sdkmessagingcampaign.DncLists {
dncListIds = append(dncListIds, *dnc.Id)
}
d.Set("dnc_list_ids", dncListIds)
}
if sdkmessagingcampaign.AlwaysRunning != nil {
d.Set("always_running", *sdkmessagingcampaign.AlwaysRunning)
}
if sdkmessagingcampaign.ContactSorts != nil {
d.Set("contact_sorts", flattenSdkOutboundMessagingCampaignContactsortSlice(*sdkmessagingcampaign.ContactSorts))
}
if sdkmessagingcampaign.MessagesPerMinute != nil {
d.Set("messages_per_minute", *sdkmessagingcampaign.MessagesPerMinute)
}
if sdkmessagingcampaign.ContactListFilters != nil {
var contactListFilterIds []string
for _, clf := range *sdkmessagingcampaign.ContactListFilters {
contactListFilterIds = append(contactListFilterIds, *clf.Id)
}
d.Set("contact_list_filter_ids", contactListFilterIds)
}
if sdkmessagingcampaign.SmsConfig != nil {
d.Set("sms_config", flattenSdkOutboundMessagingCampaignSmsconfig(sdkmessagingcampaign.SmsConfig))
}
log.Printf("Read Outbound Messagingcampaign %s %s", d.Id(), *sdkmessagingcampaign.Name)
return cc.CheckState(d)
})
}
func deleteOutboundMessagingcampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound Messagingcampaign")
_, resp, err := outboundApi.DeleteOutboundMessagingcampaign(d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete outbound Messagingcampaign %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := outboundApi.GetOutboundMessagingcampaign(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound Messagingcampaign deleted
log.Printf("Deleted Outbound Messagingcampaign %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Outbound Messagingcampaign %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Messagingcampaign %s still exists", d.Id()), resp))
})
}
func buildSdkoutboundmessagingcampaignContactsortSlice(contactSort []interface{}) *[]platformclientv2.Contactsort {
if contactSort == nil {
return nil
}
sdkContactSortSlice := make([]platformclientv2.Contactsort, 0)
for _, configContactSort := range contactSort {
var sdkContactSort platformclientv2.Contactsort
contactsortMap := configContactSort.(map[string]interface{})
if fieldName := contactsortMap["field_name"].(string); fieldName != "" {
sdkContactSort.FieldName = &fieldName
}
if direction := contactsortMap["direction"].(string); direction != "" {
sdkContactSort.Direction = &direction
}
if numeric, ok := contactsortMap["numeric"].(bool); ok {
sdkContactSort.Numeric = platformclientv2.Bool(numeric)
}
sdkContactSortSlice = append(sdkContactSortSlice, sdkContactSort)
}
return &sdkContactSortSlice
}
func buildSdkoutboundmessagingcampaignSmsconfig(smsconfig *schema.Set) *platformclientv2.Smsconfig {
if smsconfig == nil {
return nil
}
var sdkSmsconfig platformclientv2.Smsconfig
smsconfigList := smsconfig.List()
if len(smsconfigList) > 0 {
smsconfigMap := smsconfigList[0].(map[string]interface{})
if messageColumn := smsconfigMap["message_column"].(string); messageColumn != "" {
sdkSmsconfig.MessageColumn = &messageColumn
}
if phoneColumn := smsconfigMap["phone_column"].(string); phoneColumn != "" {
sdkSmsconfig.PhoneColumn = &phoneColumn
}
if senderSmsPhoneNumber := smsconfigMap["sender_sms_phone_number"].(string); senderSmsPhoneNumber != "" {
sdkSmsconfig.SenderSmsPhoneNumber = &platformclientv2.Smsphonenumberref{
PhoneNumber: &senderSmsPhoneNumber,
}
}
if contentTemplateId := smsconfigMap["content_template_id"].(string); contentTemplateId != "" {
sdkSmsconfig.ContentTemplate = &platformclientv2.Domainentityref{Id: &contentTemplateId}
}
}
return &sdkSmsconfig
}
func flattenSdkOutboundMessagingCampaignContactsortSlice(contactSorts []platformclientv2.Contactsort) []interface{} {
if len(contactSorts) == 0 {
return nil
}
contactSortList := make([]interface{}, 0)
for _, contactsort := range contactSorts {
contactSortMap := make(map[string]interface{})
if contactsort.FieldName != nil {
contactSortMap["field_name"] = *contactsort.FieldName
}
if contactsort.Direction != nil {
contactSortMap["direction"] = *contactsort.Direction
}
if contactsort.Numeric != nil {
contactSortMap["numeric"] = *contactsort.Numeric
}
contactSortList = append(contactSortList, contactSortMap)
}
return contactSortList
}
func flattenSdkOutboundMessagingCampaignSmsconfig(smsconfig *platformclientv2.Smsconfig) *schema.Set {
if smsconfig == nil {
return nil
}
smsconfigSet := schema.NewSet(schema.HashResource(outboundmessagingcampaignsmsconfigResource), []interface{}{})
smsconfigMap := make(map[string]interface{})
if smsconfig.MessageColumn != nil {
smsconfigMap["message_column"] = *smsconfig.MessageColumn
}
if smsconfig.PhoneColumn != nil {
smsconfigMap["phone_column"] = *smsconfig.PhoneColumn
}
if smsconfig.SenderSmsPhoneNumber != nil {
if smsconfig.SenderSmsPhoneNumber.PhoneNumber != nil {
smsconfigMap["sender_sms_phone_number"] = *smsconfig.SenderSmsPhoneNumber.PhoneNumber
}
}
if smsconfig.ContentTemplate != nil {
smsconfigMap["content_template_id"] = *smsconfig.ContentTemplate.Id
}
smsconfigSet.Add(smsconfigMap)
return smsconfigSet
}
func GenerateOutboundMessagingCampaignContactSort(fieldName string, direction string, numeric string) string {
if direction != "" {
direction = fmt.Sprintf(`direction = "%s"`, direction)
}
if numeric != "" {
numeric = fmt.Sprintf(`numeric = %s`, numeric)
}
return fmt.Sprintf(`
contact_sorts {
field_name = "%s"
%s
%s
}
`, fieldName, direction, numeric)
}
func validateSmsconfig(smsconfig *schema.Set) (string, bool) {
if smsconfig == nil {
return "", true
}
smsconfigList := smsconfig.List()
if len(smsconfigList) > 0 {
smsconfigMap := smsconfigList[0].(map[string]interface{})
messageColumn, _ := smsconfigMap["message_column"].(string)
contentTemplateId, _ := smsconfigMap["content_template_id"].(string)
if messageColumn == "" && contentTemplateId == "" {
return "Either message_column or content_template_id is required.", false
} else if messageColumn != "" && contentTemplateId != "" {
return "Only one of message_column or content_template_id can be defined", false
}
}
return "", true
}
package outbound_attempt_limit
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceOutboundAttemptLimit() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Outbound Attempt Limits. Select an attempt limit by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundAttemptLimitRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Attempt Limit name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceOutboundAttemptLimitRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
outboundAPI := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageNum = 1
const pageSize = 100
attemptLimits, resp, getErr := outboundAPI.GetOutboundAttemptlimits(pageSize, pageNum, true, "", name, "", "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting attempt limit %s | error: %s", name, getErr), resp))
}
if attemptLimits.Entities == nil || len(*attemptLimits.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no attempt limits found with name %s", name), resp))
}
attemptLimit := (*attemptLimits.Entities)[0]
d.SetId(*attemptLimit.Id)
return nil
})
}
func GenerateOutboundAttemptLimitDataSource(id string, attemptLimitName string, dependsOn string) string {
return fmt.Sprintf(`
data "genesyscloud_outbound_attempt_limit" "%s" {
name = "%s"
depends_on = [%s]
}
`, id, attemptLimitName, dependsOn)
}
package outbound_attempt_limit
import (
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterDataSource("genesyscloud_outbound_attempt_limit", DataSourceOutboundAttemptLimit())
regInstance.RegisterResource("genesyscloud_outbound_attempt_limit", ResourceOutboundAttemptLimit())
regInstance.RegisterExporter("genesyscloud_outbound_attempt_limit", OutboundAttemptLimitExporter())
}
package outbound_attempt_limit
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
const (
resourceName = "genesyscloud_outbound_attemptlimit"
)
var (
recallSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
`nbr_attempts`: {
Description: `Number of recall attempts. Must be less than max_attempts_per_contact.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`minutes_between_attempts`: {
Description: `Number of minutes between attempts. Must be greater than or equal to 5.`,
Required: true,
Type: schema.TypeInt,
ValidateFunc: validation.IntAtLeast(5),
},
},
}
attemptLimitRecallSettingsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`answering_machine`: {
Optional: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: recallSettings,
},
`busy`: {
Optional: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: recallSettings,
},
`fax`: {
Optional: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: recallSettings,
},
`no_answer`: {
Optional: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: recallSettings,
},
},
}
)
func getAllAttemptLimits(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
outboundAPI := platformclientv2.NewOutboundApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
attemptLimitConfigs, resp, getErr := outboundAPI.GetOutboundAttemptlimits(pageSize, pageNum, true, "", "", "", "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of attempt limit configs error: %s", getErr), resp)
}
if attemptLimitConfigs.Entities == nil || len(*attemptLimitConfigs.Entities) == 0 {
break
}
for _, attemptLimitConfig := range *attemptLimitConfigs.Entities {
resources[*attemptLimitConfig.Id] = &resourceExporter.ResourceMeta{Name: *attemptLimitConfig.Name}
}
}
return resources, nil
}
func OutboundAttemptLimitExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAttemptLimits),
}
}
func ResourceOutboundAttemptLimit() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Outbound Attempt Limit`,
CreateContext: provider.CreateWithPooledClient(createOutboundAttemptLimit),
ReadContext: provider.ReadWithPooledClient(readOutboundAttemptLimit),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundAttemptLimit),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundAttemptLimit),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name for the attempt limit.`,
Optional: true,
Type: schema.TypeString,
},
`max_attempts_per_contact`: {
Description: `The maximum number of times a contact can be called within the resetPeriod. Required if maxAttemptsPerNumber is not defined.`,
Optional: true,
Type: schema.TypeInt,
},
`max_attempts_per_number`: {
Description: `The maximum number of times a phone number can be called within the resetPeriod. Required if maxAttemptsPerContact is not defined.`,
Optional: true,
Type: schema.TypeInt,
},
`time_zone_id`: {
Description: `If the resetPeriod is TODAY, this specifies the timezone in which TODAY occurs. Required if the resetPeriod is TODAY.`,
Optional: true,
Type: schema.TypeString,
},
`reset_period`: {
Description: `After how long the number of attempts will be set back to 0.`,
Optional: true,
Type: schema.TypeString,
Default: `NEVER`,
ValidateFunc: validation.StringInSlice([]string{`NEVER`, `TODAY`}, true),
},
`recall_entries`: {
Description: `Configuration for recall attempts.`,
Optional: true,
MaxItems: 1,
Type: schema.TypeList,
Elem: attemptLimitRecallSettingsResource,
},
},
}
}
func createOutboundAttemptLimit(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
maxAttemptsPerContact := d.Get("max_attempts_per_contact").(int)
maxAttemptsPerNumber := d.Get("max_attempts_per_number").(int)
timeZoneId := d.Get("time_zone_id").(string)
resetPeriod := d.Get("reset_period").(string)
recallEntries := d.Get("recall_entries").([]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
sdkAttemptLimits := platformclientv2.Attemptlimits{}
if name != "" {
sdkAttemptLimits.Name = &name
}
if maxAttemptsPerContact != 0 {
sdkAttemptLimits.MaxAttemptsPerContact = &maxAttemptsPerContact
}
if maxAttemptsPerNumber != 0 {
sdkAttemptLimits.MaxAttemptsPerNumber = &maxAttemptsPerNumber
}
if timeZoneId != "" {
sdkAttemptLimits.TimeZoneId = &timeZoneId
}
if resetPeriod != "" {
sdkAttemptLimits.ResetPeriod = &resetPeriod
}
if recallEntries != nil && len(recallEntries) > 0 {
sdkAttemptLimits.RecallEntries = buildSdkOutboundAttemptLimitRecallEntryMap(recallEntries)
}
log.Printf("Creating Outbound Attempt Limit %s", name)
outboundAttemptLimit, resp, err := outboundApi.PostOutboundAttemptlimits(sdkAttemptLimits)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Attempt Limit %s error: %s", *sdkAttemptLimits.Name, err), resp)
}
d.SetId(*outboundAttemptLimit.Id)
log.Printf("Created Outbound Attempt Limit %s %s", name, *outboundAttemptLimit.Id)
return readOutboundAttemptLimit(ctx, d, meta)
}
func updateOutboundAttemptLimit(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
maxAttemptsPerContact := d.Get("max_attempts_per_contact").(int)
maxAttemptsPerNumber := d.Get("max_attempts_per_number").(int)
timeZoneId := d.Get("time_zone_id").(string)
resetPeriod := d.Get("reset_period").(string)
recallEntries := d.Get("recall_entries").([]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
sdkAttemptLimits := platformclientv2.Attemptlimits{}
if name != "" {
sdkAttemptLimits.Name = &name
}
if maxAttemptsPerContact != 0 {
sdkAttemptLimits.MaxAttemptsPerContact = &maxAttemptsPerContact
}
if maxAttemptsPerNumber != 0 {
sdkAttemptLimits.MaxAttemptsPerNumber = &maxAttemptsPerNumber
}
if timeZoneId != "" {
sdkAttemptLimits.TimeZoneId = &timeZoneId
}
if resetPeriod != "" {
sdkAttemptLimits.ResetPeriod = &resetPeriod
}
if recallEntries != nil && len(recallEntries) > 0 {
sdkAttemptLimits.RecallEntries = buildSdkOutboundAttemptLimitRecallEntryMap(recallEntries)
}
log.Printf("Updating Outbound Attempt Limit %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current Outbound Attempt Limit version
outboundAttemptLimit, resp, getErr := outboundApi.GetOutboundAttemptlimit(d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read outbound attempt limit %s error: %s", d.Id(), getErr), resp)
}
sdkAttemptLimits.Version = outboundAttemptLimit.Version
outboundAttemptLimit, resp, updateErr := outboundApi.PutOutboundAttemptlimit(d.Id(), sdkAttemptLimits)
if updateErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update outbound attempt limit %s error: %s", *sdkAttemptLimits.Name, updateErr), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Outbound Attempt Limit %s", name)
return readOutboundAttemptLimit(ctx, d, meta)
}
func readOutboundAttemptLimit(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundAttemptLimit(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Attempt Limit %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkAttemptLimits, resp, getErr := outboundApi.GetOutboundAttemptlimit(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Attempt Limit %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Attempt Limit %s | error: %s", d.Id(), getErr), resp))
}
if sdkAttemptLimits.Name != nil {
_ = d.Set("name", *sdkAttemptLimits.Name)
}
if sdkAttemptLimits.MaxAttemptsPerContact != nil {
_ = d.Set("max_attempts_per_contact", *sdkAttemptLimits.MaxAttemptsPerContact)
}
if sdkAttemptLimits.MaxAttemptsPerNumber != nil {
_ = d.Set("max_attempts_per_number", *sdkAttemptLimits.MaxAttemptsPerNumber)
}
if sdkAttemptLimits.TimeZoneId != nil {
_ = d.Set("time_zone_id", *sdkAttemptLimits.TimeZoneId)
}
if sdkAttemptLimits.ResetPeriod != nil {
_ = d.Set("reset_period", *sdkAttemptLimits.ResetPeriod)
}
if sdkAttemptLimits.RecallEntries != nil && len(*sdkAttemptLimits.RecallEntries) > 0 {
_ = d.Set("recall_entries", flattenSdkOutboundAttemptLimitRecallEntry(sdkAttemptLimits.RecallEntries))
}
log.Printf("Read Outbound Attempt Limit %s %s", d.Id(), *sdkAttemptLimits.Name)
return cc.CheckState(d)
})
}
func deleteOutboundAttemptLimit(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound Attempt Limit")
resp, err := outboundApi.DeleteOutboundAttemptlimit(d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete outbound attempt limit %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := outboundApi.GetOutboundAttemptlimit(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound Attempt Limit deleted
log.Printf("Deleted Outbound Attempt Limit %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Attempt Limit %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Attempt Limit %s still exists", d.Id()), resp))
})
}
func buildSdkOutboundAttemptLimitRecallEntryMap(recallEntries []interface{}) *map[string]platformclientv2.Recallentry {
if len(recallEntries) == 0 {
return nil
}
recallEntriesMap := map[string]platformclientv2.Recallentry{}
if entriesMap, ok := recallEntries[0].(map[string]interface{}); ok {
types := []string{"busy", "no_answer", "answering_machine", "fax"}
for _, t := range types {
entrySet := entriesMap[t].(*schema.Set).List()
if len(entrySet) == 0 {
continue
}
if entryMap, ok := entrySet[0].(map[string]interface{}); ok && len(entryMap) > 0 {
recallEntriesMap[util.ToCamelCase(t)] = *buildSdkRecallEntry(entryMap)
}
}
}
return &recallEntriesMap
}
func buildSdkRecallEntry(entry map[string]interface{}) *platformclientv2.Recallentry {
sdkRecallEntry := platformclientv2.Recallentry{}
if nbrAttempts, ok := entry["nbr_attempts"].(int); ok {
sdkRecallEntry.NbrAttempts = &nbrAttempts
}
if minsBetweenAttempts, ok := entry["minutes_between_attempts"].(int); ok {
sdkRecallEntry.MinutesBetweenAttempts = &minsBetweenAttempts
}
return &sdkRecallEntry
}
func flattenSdkOutboundAttemptLimitRecallEntry(sdkRecallEntries *map[string]platformclientv2.Recallentry) []interface{} {
recallEntries := make(map[string]interface{})
for key, val := range *sdkRecallEntries {
recallEntries[util.ToSnakeCase(key)] = flattenSdkRecallEntry(val)
}
return []interface{}{recallEntries}
}
func flattenSdkRecallEntry(sdkEntry platformclientv2.Recallentry) *schema.Set {
var (
entryMap = make(map[string]interface{})
entrySet = schema.NewSet(schema.HashResource(recallSettings), []interface{}{})
)
entryMap["nbr_attempts"] = *sdkEntry.NbrAttempts
entryMap["minutes_between_attempts"] = *sdkEntry.MinutesBetweenAttempts
entrySet.Add(entryMap)
return entrySet
}
func GenerateAttemptLimitResource(
resourceId string,
name string,
maxAttemptsPerContact string,
maxAttemptsPerNumber string,
timeZoneId string,
resetPeriod string,
nestedBlocks ...string,
) string {
if maxAttemptsPerContact != "" {
maxAttemptsPerContact = fmt.Sprintf(`max_attempts_per_contact = %s`, maxAttemptsPerContact)
}
if maxAttemptsPerNumber != "" {
maxAttemptsPerNumber = fmt.Sprintf(`max_attempts_per_number = %s`, maxAttemptsPerNumber)
}
if timeZoneId != "" {
timeZoneId = fmt.Sprintf(`time_zone_id = "%s"`, timeZoneId)
}
if resetPeriod != "" {
resetPeriod = fmt.Sprintf(`reset_period = "%s"`, resetPeriod)
}
return fmt.Sprintf(`
resource "genesyscloud_outbound_attempt_limit" "%s" {
name = "%s"
%s
%s
%s
%s
%s
}
`, resourceId, name, maxAttemptsPerContact, maxAttemptsPerNumber, timeZoneId, resetPeriod, strings.Join(nestedBlocks, "\n"))
}
package outbound_callabletimeset
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_outbound_callabletimeset.go contains the data source implementation for the resource.
*/
// dataSourceOutboundCallabletimesetRead retrieves by name term the id in question
func dataSourceOutboundCallabletimesetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallabletimesetProxy(sdkConfig)
timesetName := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
timesetId, retryable, resp, err := proxy.getOutboundCallabletimesetByName(ctx, timesetName)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting callable timeset %s | error: %s", timesetName, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no callable timeset found with timesetName %s", timesetName), resp))
}
d.SetId(timesetId)
return nil
})
}
package outbound_callabletimeset
import (
"context"
"fmt"
"log"
"strings"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_outbound_callabletimeset_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundCallableTimesetProxy
// type definitions for each func on our proxy
type createOutboundCallabletimesetFunc func(ctx context.Context, p *outboundCallableTimesetProxy, timeset *platformclientv2.Callabletimeset) (*platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error)
type getAllOutboundCallableTimesetFunc func(ctx context.Context, p *outboundCallableTimesetProxy) (*[]platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error)
type getOutboundCallabletimesetByIdFunc func(ctx context.Context, p *outboundCallableTimesetProxy, timesetId string) (timeset *platformclientv2.Callabletimeset, response *platformclientv2.APIResponse, err error)
type getOutboundCallabletimesetByNameFunc func(ctx context.Context, p *outboundCallableTimesetProxy, name string) (timesetId string, retryable bool, response *platformclientv2.APIResponse, err error)
type updateOutboundCallabletimesetFunc func(ctx context.Context, p *outboundCallableTimesetProxy, timesetId string, timeset *platformclientv2.Callabletimeset) (*platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error)
type deleteOutboundCallabletimesetFunc func(ctx context.Context, p *outboundCallableTimesetProxy, timesetId string) (response *platformclientv2.APIResponse, err error)
// outboundCallableTimesetProxy contains all of the methods that call genesys cloud APIs
type outboundCallableTimesetProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundCallabletimesetAttr createOutboundCallabletimesetFunc
getAllOutboundCallableTimesetAttr getAllOutboundCallableTimesetFunc
getOutboundCallabletimesetByIdAttr getOutboundCallabletimesetByIdFunc
getOutboundCallabletimesetByNameAttr getOutboundCallabletimesetByNameFunc
updateOutboundCallabletimesetAttr updateOutboundCallabletimesetFunc
deleteOutboundCallabletimesetAttr deleteOutboundCallabletimesetFunc
}
// newOutboundCallableTimesetProxy initializes the timeset proxy with the data needed for communication with the genesys cloud
func newOutboundCallableTimesetProxy(clientConfig *platformclientv2.Configuration) *outboundCallableTimesetProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundCallableTimesetProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundCallabletimesetAttr: createOutboundCallabletimesetFn,
getAllOutboundCallableTimesetAttr: getAllOutboundCallableTimesetFn,
getOutboundCallabletimesetByIdAttr: getOutboundCallabletimesetByIdFn,
getOutboundCallabletimesetByNameAttr: getOutboundCallabletimesetByNameFn,
updateOutboundCallabletimesetAttr: updateOutboundCallabletimesetFn,
deleteOutboundCallabletimesetAttr: deleteOutboundCallabletimesetFn,
}
}
func getOutboundCallabletimesetProxy(clientConfig *platformclientv2.Configuration) *outboundCallableTimesetProxy {
if internalProxy == nil {
internalProxy = newOutboundCallableTimesetProxy(clientConfig)
}
return internalProxy
}
// createOutboundCallabletimeset creates a Genesys Cloud Outbound Callable Timeset
func (p *outboundCallableTimesetProxy) createOutboundCallabletimeset(ctx context.Context, timeset *platformclientv2.Callabletimeset) (*platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error) {
return p.createOutboundCallabletimesetAttr(ctx, p, timeset)
}
// getAllOutboundCallableTimeset retrieves all Genesys Cloud Outbound Callable Timesets
func (p *outboundCallableTimesetProxy) getAllOutboundCallableTimeset(ctx context.Context) (*[]platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error) {
return p.getAllOutboundCallableTimesetAttr(ctx, p)
}
// getOutboundCallabletimesetById returns a single Genesys Cloud Outbound Callable Timeset by Id
func (p *outboundCallableTimesetProxy) getOutboundCallabletimesetById(ctx context.Context, timesetId string) (timeset *platformclientv2.Callabletimeset, response *platformclientv2.APIResponse, err error) {
return p.getOutboundCallabletimesetByIdAttr(ctx, p, timesetId)
}
// getOutboundCallabletimesetByName returns a single Genesys Cloud Outbound Callable Timeset by a name
func (p *outboundCallableTimesetProxy) getOutboundCallabletimesetByName(ctx context.Context, name string) (timesetId string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundCallabletimesetByNameAttr(ctx, p, name)
}
// updateOutboundCallabletimeset updates a Genesys Cloud Outbound Callable Timeset
func (p *outboundCallableTimesetProxy) updateOutboundCallabletimeset(ctx context.Context, timesetId string, timeset *platformclientv2.Callabletimeset) (*platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error) {
return p.updateOutboundCallabletimesetAttr(ctx, p, timesetId, timeset)
}
// deleteOutboundCallabletimeset deletes a Genesys Cloud Outbound Callable timeset by Id
func (p *outboundCallableTimesetProxy) deleteOutboundCallabletimeset(ctx context.Context, timesetId string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundCallabletimesetAttr(ctx, p, timesetId)
}
// createOutboundCallabletimesetFn is an implementation function for creating a Genesys Cloud Outbound Callable Timeset
func createOutboundCallabletimesetFn(ctx context.Context, p *outboundCallableTimesetProxy, timeset *platformclientv2.Callabletimeset) (*platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error) {
timeset, resp, err := p.outboundApi.PostOutboundCallabletimesets(*timeset)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create timeset: %s", err)
}
return timeset, resp, nil
}
// getAllOutboundCallableTimesetFn is the implementation for retrieving all outbound callable timesets in Genesys Cloud
func getAllOutboundCallableTimesetFn(ctx context.Context, p *outboundCallableTimesetProxy) (*[]platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error) {
var allCallableTimesets []platformclientv2.Callabletimeset
timesets, resp, err := p.outboundApi.GetOutboundCallabletimesets(100, 1, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get outbound timesets: %v", err)
}
if timesets.Entities == nil || len(*timesets.Entities) == 0 {
return &allCallableTimesets, resp, nil
}
for _, timeset := range *timesets.Entities {
allCallableTimesets = append(allCallableTimesets, timeset)
}
var response *platformclientv2.APIResponse
for pageNum := 2; pageNum <= *timesets.PageCount; pageNum++ {
const pageSize = 100
timesets, resp, err := p.outboundApi.GetOutboundCallabletimesets(pageSize, pageNum, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get outbound timesets: %v", err)
}
response = resp
if timesets.Entities == nil || len(*timesets.Entities) == 0 {
break
}
for _, timeset := range *timesets.Entities {
log.Printf("Dealing with timeset id : %s", *timeset.Id)
allCallableTimesets = append(allCallableTimesets, timeset)
}
}
return &allCallableTimesets, response, nil
}
// getOutboundCallabletimesetByIdFn is an implementation of the function to get a Genesys Cloud Outbound Callabletimeset by Id
func getOutboundCallabletimesetByIdFn(ctx context.Context, p *outboundCallableTimesetProxy, timesetId string) (timeset *platformclientv2.Callabletimeset, response *platformclientv2.APIResponse, err error) {
timeset, resp, err := p.outboundApi.GetOutboundCallabletimeset(timesetId)
if err != nil {
//This is an API that throws an error on a 404 instead of just returning a 404.
if strings.Contains(fmt.Sprintf("%s", err), "API Error: 404") {
return nil, resp, nil
}
return nil, resp, fmt.Errorf("Failed to retrieve timeset by id %s: %s", timesetId, err)
}
return timeset, resp, nil
}
// getOutboundCallabletimesetIdByNameFn is an implementation of the function to get a Genesys Cloud Outbound Callabletimeset by name
func getOutboundCallabletimesetByNameFn(ctx context.Context, p *outboundCallableTimesetProxy, name string) (timesetId string, retryable bool, response *platformclientv2.APIResponse, err error) {
timesets, resp, err := getAllOutboundCallableTimesetFn(ctx, p)
if err != nil {
return "", false, resp, fmt.Errorf("Error searching outbound timeset %s: %s", name, err)
}
var timeset platformclientv2.Callabletimeset
for _, timesetSdk := range *timesets {
if *timesetSdk.Name == name {
log.Printf("Retrieved the timeset id %s by name %s", *timesetSdk.Id, name)
timeset = timesetSdk
return *timeset.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find timeset with name %s", name)
}
// updateOutboundCallabletimesetFn is an implementation of the function to update a Genesys Cloud Outbound Callabletimesets
func updateOutboundCallabletimesetFn(ctx context.Context, p *outboundCallableTimesetProxy, timesetId string, timeset *platformclientv2.Callabletimeset) (*platformclientv2.Callabletimeset, *platformclientv2.APIResponse, error) {
outboundCallabletimeset, resp, err := getOutboundCallabletimesetByIdFn(ctx, p, timesetId)
if err != nil {
return nil, resp, fmt.Errorf("Failed to timeset by id %s: %s", timesetId, err)
}
timeset.Version = outboundCallabletimeset.Version
timeset, resp, err = p.outboundApi.PutOutboundCallabletimeset(timesetId, *timeset)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update timeset: %s", err)
}
return timeset, resp, nil
}
// deleteOutboundCallabletimesetFn is an implementation function for deleting a Genesys Cloud Outbound Callabletimesets
func deleteOutboundCallabletimesetFn(ctx context.Context, p *outboundCallableTimesetProxy, timesetId string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.outboundApi.DeleteOutboundCallabletimeset(timesetId)
if err != nil {
return resp, fmt.Errorf("Failed to delete timeset: %s", err)
}
return resp, nil
}
package outbound_callabletimeset
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_outbound_callabletimeset.go contains all of the methods that perform the core logic for a resource.
*/
// getAllOutboundCallableTimesets retrieves all of the Outbound Callable Timesets via Terraform in the genesys cloud and is used for the exporter
func getAllOutboundCallableTimesets(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getOutboundCallabletimesetProxy(clientConfig)
callabletimesets, resp, getErr := proxy.getAllOutboundCallableTimeset(ctx)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of callable timeset configs error: %s", getErr), resp)
}
for _, callabletimesets := range *callabletimesets {
resources[*callabletimesets.Id] = &resourceExporter.ResourceMeta{Name: *callabletimesets.Name}
}
return resources, nil
}
// createOutboundCallabletimeset is used by the outbound_callabletimeset resource to create Outbound Callable Timesets
func createOutboundCallabletimeset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallabletimesetProxy(sdkConfig)
callableTimeset := getOutboundCallableTimesetFromResourceData(d)
log.Printf("Creating Outbound Callabletimeset %s", name)
outboundCallabletimeset, resp, err := proxy.createOutboundCallabletimeset(ctx, &callableTimeset)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Callabletimeset %s error: %s", *callableTimeset.Name, err), resp)
}
d.SetId(*outboundCallabletimeset.Id)
log.Printf("Created Outbound Callabletimeset %s %s", name, *outboundCallabletimeset.Id)
return readOutboundCallabletimeset(ctx, d, meta)
}
// updateOutboundCallabletimeset is used by the outbound_callabletimeset resource to update an Outbound Callable Timeset
func updateOutboundCallabletimeset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallabletimesetProxy(sdkConfig)
callableTimeset := getOutboundCallableTimesetFromResourceData(d)
log.Printf("Updating Outbound Callabletimeset %s", d.Id())
outboundCallabletimeset, resp, err := proxy.updateOutboundCallabletimeset(ctx, d.Id(), &callableTimeset)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound Callabletimeset %s error: %s", *callableTimeset.Name, err), resp)
}
log.Printf("Updated Outbound Callabletimeset %s", *outboundCallabletimeset.Id)
return readOutboundCallabletimeset(ctx, d, meta)
}
// readOutboundCallabletimeset is used by the outbound_callabletimeset resource to read an Outbound Callable Timeset from the genesys cloud
func readOutboundCallabletimeset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallabletimesetProxy(sdkConfig)
log.Printf("Reading Outbound Callabletimeset %s", d.Id())
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundCallabletimeset(), constants.DefaultConsistencyChecks, resourceName)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
callableTimeset, resp, getErr := proxy.getOutboundCallabletimesetById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Callabletimeset %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Callabletimeset %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", callableTimeset.Name)
if callableTimeset.CallableTimes != nil {
// Remove the milliseconds added to start_time and stop_time by the API
trimTime(callableTimeset.CallableTimes)
d.Set("callable_times", flattenCallableTimes(*callableTimeset.CallableTimes))
}
log.Printf("Read Outbound Callabletimeset %s %s", d.Id(), *callableTimeset.Name)
return cc.CheckState(d)
})
}
// deleteOutboundCallabletimeset is used by the outbound_callabletimeset resource to delete an existing Outbound Callable Timeset from the genesys cloud
func deleteOutboundCallabletimeset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallabletimesetProxy(sdkConfig)
log.Printf("Deleting Outbound Callabletimeset")
resp, err := proxy.deleteOutboundCallabletimeset(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound Callabletimeset %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
resp, err := proxy.deleteOutboundCallabletimeset(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound Callabletimeset deleted
log.Printf("Deleted Outbound Callabletimeset %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Outbound Callabletimeset %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Callabletimeset %s still exists", d.Id()), resp))
})
}
package outbound_callabletimeset
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
const resourceName = "genesyscloud_outbound_callabletimeset"
// SetRegistrar registers all of the resources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceOutboundCallabletimeset())
l.RegisterResource(resourceName, ResourceOutboundCallabletimeset())
l.RegisterExporter(resourceName, OutboundCallableTimesetExporter())
}
var campaignTimeslotResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`start_time`: {
Description: `The start time of the interval as an ISO-8601 string, i.e. HH:mm:ss`,
Required: true,
ValidateDiagFunc: gcloud.ValidateTime,
Type: schema.TypeString,
},
`stop_time`: {
Description: `The end time of the interval as an ISO-8601 string, i.e. HH:mm:ss`,
Required: true,
ValidateDiagFunc: gcloud.ValidateTime,
Type: schema.TypeString,
},
`day`: {
Description: `The day of the interval. Valid values: [1-7], representing Monday through Sunday`,
Required: true,
ValidateFunc: validation.IntInSlice([]int{1, 2, 3, 4, 5, 6, 7}),
Type: schema.TypeInt,
},
},
}
var timeSlotResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`time_slots`: {
Description: `The time intervals for which it is acceptable to place outbound calls.`,
Required: true,
Type: schema.TypeSet,
Elem: campaignTimeslotResource,
},
`time_zone_id`: {
Description: `The time zone for the time slots; for example, Africa/Abidjan`,
Required: true,
Type: schema.TypeString,
},
},
}
// ResourceOutboundCallabletimeset registers the genesyscloud_outbound_callabletimeset resource with Terraform
func ResourceOutboundCallabletimeset() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud outbound callabletimeset`,
CreateContext: provider.CreateWithPooledClient(createOutboundCallabletimeset),
ReadContext: provider.ReadWithPooledClient(readOutboundCallabletimeset),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundCallabletimeset),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundCallabletimeset),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the CallableTimeSet.`,
Required: true,
Type: schema.TypeString,
},
`callable_times`: {
Description: `The list of CallableTimes for which it is acceptable to place outbound calls.`,
Required: true,
Type: schema.TypeSet,
Elem: timeSlotResource,
},
},
}
}
// OutboundCallableTimesetExporter returns the resourceExporter object used to hold the genesyscloud_outbound_callabletimeset exporter's config
func OutboundCallableTimesetExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundCallableTimesets),
}
}
// dataSourceOutboundCallabletimeset registers the genesyscloud_outbound_callabletimeset data source
func DataSourceOutboundCallabletimeset() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Clound Outbound Callable Timesets. Select a callable timeset by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundCallabletimesetRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Callable timeset name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package outbound_callabletimeset
import (
"fmt"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_outbound_callabletimeset_utils.go file contains helper methods to marshal and unmarshal data into formats consumable by Terraform and/or Genesys Cloud
*/
func getOutboundCallableTimesetFromResourceData(d *schema.ResourceData) platformclientv2.Callabletimeset {
name := d.Get("name").(string)
return platformclientv2.Callabletimeset{
Name: &name,
CallableTimes: buildCallableTimes(d.Get("callable_times").(*schema.Set)),
}
}
func trimTime(values *[]platformclientv2.Callabletime) {
for _, value := range *values {
for _, slot := range *value.TimeSlots {
startTime := *slot.StartTime
*slot.StartTime = startTime[:8]
stopTime := *slot.StopTime
*slot.StopTime = stopTime[:8]
}
}
}
func buildCampaignTimeslots(campaigntimeslot *schema.Set) *[]platformclientv2.Campaigntimeslot {
if campaigntimeslot == nil {
return nil
}
sdkCampaigntimeslotSlice := make([]platformclientv2.Campaigntimeslot, 0)
campaigntimeslotList := campaigntimeslot.List()
for _, configcampaigntimeslot := range campaigntimeslotList {
var sdkCampaigntimeslot platformclientv2.Campaigntimeslot
campaigntimeslotMap := configcampaigntimeslot.(map[string]interface{})
if startTime := campaigntimeslotMap["start_time"].(string); startTime != "" {
sdkCampaigntimeslot.StartTime = &startTime
}
if stopTime := campaigntimeslotMap["stop_time"].(string); stopTime != "" {
sdkCampaigntimeslot.StopTime = &stopTime
}
sdkCampaigntimeslot.Day = platformclientv2.Int(campaigntimeslotMap["day"].(int))
sdkCampaigntimeslotSlice = append(sdkCampaigntimeslotSlice, sdkCampaigntimeslot)
}
return &sdkCampaigntimeslotSlice
}
func buildCallableTimes(callabletime *schema.Set) *[]platformclientv2.Callabletime {
if callabletime == nil {
return nil
}
sdkCallabletimeSlice := make([]platformclientv2.Callabletime, 0)
callabletimeList := callabletime.List()
for _, configcallabletime := range callabletimeList {
var sdkCallabletime platformclientv2.Callabletime
callabletimeMap := configcallabletime.(map[string]interface{})
if timeSlots := callabletimeMap["time_slots"]; timeSlots != nil {
sdkCallabletime.TimeSlots = buildCampaignTimeslots(timeSlots.(*schema.Set))
}
if timeZoneId := callabletimeMap["time_zone_id"].(string); timeZoneId != "" {
sdkCallabletime.TimeZoneId = &timeZoneId
}
sdkCallabletimeSlice = append(sdkCallabletimeSlice, sdkCallabletime)
}
return &sdkCallabletimeSlice
}
func flattenCampaignTimeslots(campaigntimeslots []platformclientv2.Campaigntimeslot) *schema.Set {
if len(campaigntimeslots) == 0 {
return nil
}
campaigntimeslotSet := schema.NewSet(schema.HashResource(campaignTimeslotResource), []interface{}{})
for _, campaigntimeslot := range campaigntimeslots {
campaigntimeslotMap := make(map[string]interface{})
if campaigntimeslot.StartTime != nil {
campaigntimeslotMap["start_time"] = *campaigntimeslot.StartTime
}
if campaigntimeslot.StopTime != nil {
campaigntimeslotMap["stop_time"] = *campaigntimeslot.StopTime
}
if campaigntimeslot.Day != nil {
campaigntimeslotMap["day"] = *campaigntimeslot.Day
}
campaigntimeslotSet.Add(campaigntimeslotMap)
}
return campaigntimeslotSet
}
func flattenCallableTimes(callabletimes []platformclientv2.Callabletime) *schema.Set {
if len(callabletimes) == 0 {
return nil
}
callabletimeSet := schema.NewSet(schema.HashResource(timeSlotResource), []interface{}{})
for _, callabletime := range callabletimes {
callabletimeMap := make(map[string]interface{})
if callabletime.TimeSlots != nil {
callabletimeMap["time_slots"] = flattenCampaignTimeslots(*callabletime.TimeSlots)
}
if callabletime.TimeZoneId != nil {
callabletimeMap["time_zone_id"] = *callabletime.TimeZoneId
}
callabletimeSet.Add(callabletimeMap)
}
return callabletimeSet
}
func GenerateOutboundCallabletimeset(
resourceId string,
name string,
nestedBlocks ...string) string {
return fmt.Sprintf(`
resource "genesyscloud_outbound_callabletimeset" "%s"{
name = "%s"
%s
}
`, resourceId, name, strings.Join(nestedBlocks, "\n"),
)
}
func GenerateCallableTimesBlock(
timeZoneID string,
attrs ...string) string {
return fmt.Sprintf(`
callable_times {
time_zone_id = "%s"
%s
}
`, timeZoneID, strings.Join(attrs, "\n"))
}
func GenerateTimeSlotsBlock(
startTime string,
stopTime string,
day string) string {
return fmt.Sprintf(`
time_slots {
start_time = "%s"
stop_time = "%s"
day = %s
}
`, startTime, stopTime, day)
}
package outbound_callanalysisresponseset
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_outbound_callanalysisresponseset.go contains the data source implementation
for the resource.
*/
// dataSourceOutboundCallanalysisresponsesetRead retrieves by name the id in question
func dataSourceOutboundCallanalysisresponsesetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundCallanalysisresponsesetProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
responseSetId, retryable, resp, err := proxy.getOutboundCallanalysisresponsesetIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching outbound callanalysisresponseset %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No outbound callanalysisresponseset found with name %s", name), resp))
}
d.SetId(responseSetId)
return nil
})
}
package outbound_callanalysisresponseset
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
)
/*
The genesyscloud_outbound_callanalysisresponseset_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundCallanalysisresponsesetProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOutboundCallanalysisresponsesetFunc func(ctx context.Context, p *outboundCallanalysisresponsesetProxy, responseSet *platformclientv2.Responseset) (*platformclientv2.Responseset, *platformclientv2.APIResponse, error)
type getAllOutboundCallanalysisresponsesetFunc func(ctx context.Context, p *outboundCallanalysisresponsesetProxy, name string) (*[]platformclientv2.Responseset, *platformclientv2.APIResponse, error)
type getOutboundCallanalysisresponsesetIdByNameFunc func(ctx context.Context, p *outboundCallanalysisresponsesetProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getOutboundCallanalysisresponsesetByIdFunc func(ctx context.Context, p *outboundCallanalysisresponsesetProxy, id string) (responseSet *platformclientv2.Responseset, response *platformclientv2.APIResponse, err error)
type updateOutboundCallanalysisresponsesetFunc func(ctx context.Context, p *outboundCallanalysisresponsesetProxy, id string, responseSet *platformclientv2.Responseset) (*platformclientv2.Responseset, *platformclientv2.APIResponse, error)
type deleteOutboundCallanalysisresponsesetFunc func(ctx context.Context, p *outboundCallanalysisresponsesetProxy, id string) (response *platformclientv2.APIResponse, err error)
// outboundCallanalysisresponsesetProxy contains all of the methods that call genesys cloud APIs.
type outboundCallanalysisresponsesetProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundCallanalysisresponsesetAttr createOutboundCallanalysisresponsesetFunc
getAllOutboundCallanalysisresponsesetAttr getAllOutboundCallanalysisresponsesetFunc
getOutboundCallanalysisresponsesetIdByNameAttr getOutboundCallanalysisresponsesetIdByNameFunc
getOutboundCallanalysisresponsesetByIdAttr getOutboundCallanalysisresponsesetByIdFunc
updateOutboundCallanalysisresponsesetAttr updateOutboundCallanalysisresponsesetFunc
deleteOutboundCallanalysisresponsesetAttr deleteOutboundCallanalysisresponsesetFunc
}
// newOutboundCallanalysisresponsesetProxy initializes the outbound callanalysisresponseset proxy with all of the data needed to communicate with Genesys Cloud
func newOutboundCallanalysisresponsesetProxy(clientConfig *platformclientv2.Configuration) *outboundCallanalysisresponsesetProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundCallanalysisresponsesetProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundCallanalysisresponsesetAttr: createOutboundCallanalysisresponsesetFn,
getAllOutboundCallanalysisresponsesetAttr: getAllOutboundCallanalysisresponsesetFn,
getOutboundCallanalysisresponsesetIdByNameAttr: getOutboundCallanalysisresponsesetIdByNameFn,
getOutboundCallanalysisresponsesetByIdAttr: getOutboundCallanalysisresponsesetByIdFn,
updateOutboundCallanalysisresponsesetAttr: updateOutboundCallanalysisresponsesetFn,
deleteOutboundCallanalysisresponsesetAttr: deleteOutboundCallanalysisresponsesetFn,
}
}
// getOutboundCallanalysisresponsesetProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundCallanalysisresponsesetProxy(clientConfig *platformclientv2.Configuration) *outboundCallanalysisresponsesetProxy {
if internalProxy == nil {
internalProxy = newOutboundCallanalysisresponsesetProxy(clientConfig)
}
return internalProxy
}
// createOutboundCallanalysisresponseset creates a Genesys Cloud outbound callanalysisresponseset
func (p *outboundCallanalysisresponsesetProxy) createOutboundCallanalysisresponseset(ctx context.Context, outboundCallanalysisresponseset *platformclientv2.Responseset) (*platformclientv2.Responseset, *platformclientv2.APIResponse, error) {
return p.createOutboundCallanalysisresponsesetAttr(ctx, p, outboundCallanalysisresponseset)
}
// getOutboundCallanalysisresponseset retrieves all Genesys Cloud outbound callanalysisresponseset
func (p *outboundCallanalysisresponsesetProxy) getAllOutboundCallanalysisresponseset(ctx context.Context) (*[]platformclientv2.Responseset, *platformclientv2.APIResponse, error) {
return p.getAllOutboundCallanalysisresponsesetAttr(ctx, p, "")
}
// getOutboundCallanalysisresponsesetIdByName returns a single Genesys Cloud outbound callanalysisresponseset by a name
func (p *outboundCallanalysisresponsesetProxy) getOutboundCallanalysisresponsesetIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundCallanalysisresponsesetIdByNameAttr(ctx, p, name)
}
// getOutboundCallanalysisresponsesetById returns a single Genesys Cloud outbound callanalysisresponseset by Id
func (p *outboundCallanalysisresponsesetProxy) getOutboundCallanalysisresponsesetById(ctx context.Context, id string) (outboundCallanalysisresponseset *platformclientv2.Responseset, response *platformclientv2.APIResponse, err error) {
return p.getOutboundCallanalysisresponsesetByIdAttr(ctx, p, id)
}
// updateOutboundCallanalysisresponseset updates a Genesys Cloud outbound callanalysisresponseset
func (p *outboundCallanalysisresponsesetProxy) updateOutboundCallanalysisresponseset(ctx context.Context, id string, outboundCallanalysisresponseset *platformclientv2.Responseset) (*platformclientv2.Responseset, *platformclientv2.APIResponse, error) {
return p.updateOutboundCallanalysisresponsesetAttr(ctx, p, id, outboundCallanalysisresponseset)
}
// deleteOutboundCallanalysisresponseset deletes a Genesys Cloud outbound callanalysisresponseset by Id
func (p *outboundCallanalysisresponsesetProxy) deleteOutboundCallanalysisresponseset(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundCallanalysisresponsesetAttr(ctx, p, id)
}
// createOutboundCallanalysisresponsesetFn is an implementation function for creating a Genesys Cloud outbound callanalysisresponseset
func createOutboundCallanalysisresponsesetFn(ctx context.Context, p *outboundCallanalysisresponsesetProxy, outboundCallanalysisresponseset *platformclientv2.Responseset) (*platformclientv2.Responseset, *platformclientv2.APIResponse, error) {
responseSet, resp, err := p.outboundApi.PostOutboundCallanalysisresponsesets(*outboundCallanalysisresponseset)
if err != nil {
return nil, resp, err
}
return responseSet, resp, nil
}
// getAllOutboundCallanalysisresponsesetFn is the implementation for retrieving all outbound callanalysisresponseset in Genesys Cloud
func getAllOutboundCallanalysisresponsesetFn(ctx context.Context, p *outboundCallanalysisresponsesetProxy, name string) (*[]platformclientv2.Responseset, *platformclientv2.APIResponse, error) {
var allResponseSets []platformclientv2.Responseset
const pageSize = 100
responseSets, resp, err := p.outboundApi.GetOutboundCallanalysisresponsesets(pageSize, 1, true, "", name, "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get response set: %v", err)
}
if responseSets.Entities == nil || len(*responseSets.Entities) == 0 {
return &allResponseSets, resp, nil
}
for _, responseSet := range *responseSets.Entities {
allResponseSets = append(allResponseSets, responseSet)
}
for pageNum := 2; pageNum <= *responseSets.PageCount; pageNum++ {
responseSets, resp, err := p.outboundApi.GetOutboundCallanalysisresponsesets(pageSize, pageNum, true, "", name, "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get response set: %v", err)
}
if responseSets.Entities == nil || len(*responseSets.Entities) == 0 {
break
}
for _, responseSet := range *responseSets.Entities {
allResponseSets = append(allResponseSets, responseSet)
}
}
return &allResponseSets, resp, nil
}
// getOutboundCallanalysisresponsesetIdByNameFn is an implementation of the function to get a Genesys Cloud outbound callanalysisresponseset by name
func getOutboundCallanalysisresponsesetIdByNameFn(ctx context.Context, p *outboundCallanalysisresponsesetProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
responseSets, resp, err := getAllOutboundCallanalysisresponsesetFn(ctx, p, name)
if err != nil {
return "", false, resp, err
}
if responseSets == nil || len(*responseSets) == 0 {
return "", true, resp, fmt.Errorf("No outbound callanalysisresponseset found with name %s", name)
}
for _, responseSet := range *responseSets {
if *responseSet.Name == name {
log.Printf("Retrieved the outbound callanalysisresponseset id %s by name %s", *responseSet.Id, name)
return *responseSet.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find outbound callanalysisresponseset with name %s", name)
}
// getOutboundCallanalysisresponsesetByIdFn is an implementation of the function to get a Genesys Cloud outbound callanalysisresponseset by Id
func getOutboundCallanalysisresponsesetByIdFn(ctx context.Context, p *outboundCallanalysisresponsesetProxy, id string) (outboundCallanalysisresponseset *platformclientv2.Responseset, response *platformclientv2.APIResponse, err error) {
responseSet, resp, err := p.outboundApi.GetOutboundCallanalysisresponseset(id)
if err != nil {
return nil, resp, err
}
return responseSet, resp, nil
}
// updateOutboundCallanalysisresponsesetFn is an implementation of the function to update a Genesys Cloud outbound callanalysisresponseset
func updateOutboundCallanalysisresponsesetFn(ctx context.Context, p *outboundCallanalysisresponsesetProxy, id string, outboundCallanalysisresponseset *platformclientv2.Responseset) (*platformclientv2.Responseset, *platformclientv2.APIResponse, error) {
responseSet, resp, err := getOutboundCallanalysisresponsesetByIdFn(ctx, p, id)
if err != nil {
return nil, resp, err
}
outboundCallanalysisresponseset.Version = responseSet.Version
outboundCallanalysisresponseset, resp, err = p.outboundApi.PutOutboundCallanalysisresponseset(id, *outboundCallanalysisresponseset)
if err != nil {
return nil, resp, err
}
return outboundCallanalysisresponseset, resp, nil
}
// deleteOutboundCallanalysisresponsesetFn is an implementation function for deleting a Genesys Cloud outbound callanalysisresponseset
func deleteOutboundCallanalysisresponsesetFn(ctx context.Context, p *outboundCallanalysisresponsesetProxy, id string) (response *platformclientv2.APIResponse, err error) {
return p.outboundApi.DeleteOutboundCallanalysisresponseset(id)
}
package outbound_callanalysisresponseset
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
)
/*
The resource_genesyscloud_outbound_callanalysisresponseset.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthOutboundCallanalysisresponseset retrieves all of the outbound callanalysisresponseset via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthOutboundCallanalysisresponsesets(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getOutboundCallanalysisresponsesetProxy(clientConfig)
responseSets, resp, getErr := proxy.getAllOutboundCallanalysisresponseset(ctx)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of call analysis response set configs error: %s", getErr), resp)
}
for _, responseSet := range *responseSets {
resources[*responseSet.Id] = &resourceExporter.ResourceMeta{Name: *responseSet.Name}
}
return resources, nil
}
// createOutboundCallanalysisresponseset is used by the outbound_callanalysisresponseset resource to create Genesys cloud outbound callanalysisresponseset
func createOutboundCallanalysisresponseset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallanalysisresponsesetProxy(sdkConfig)
responseSet := getResponseSetFromResourceData(d)
log.Printf("Creating Outbound Call Analysis Response Set %s", *responseSet.Name)
outboundCallanalysisresponseset, resp, err := proxy.createOutboundCallanalysisresponseset(ctx, &responseSet)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Call Analysis Response Set %s error: %s", *responseSet.Name, err), resp)
}
d.SetId(*outboundCallanalysisresponseset.Id)
log.Printf("Created Outbound Call Analysis Response Set %s %s", *responseSet.Name, *outboundCallanalysisresponseset.Id)
return readOutboundCallanalysisresponseset(ctx, d, meta)
}
// readOutboundCallanalysisresponseset is used by the outbound_callanalysisresponseset resource to read an outbound callanalysisresponseset from genesys cloud
func readOutboundCallanalysisresponseset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallanalysisresponsesetProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundCallanalysisresponseset(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Call Analysis Response Set %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
responseSet, resp, getErr := proxy.getOutboundCallanalysisresponsesetById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Call Analysis Response Set %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Call Analysis Response Set %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", responseSet.Name)
resourcedata.SetNillableValue(d, "beep_detection_enabled", responseSet.BeepDetectionEnabled)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "responses", responseSet.Responses, flattenSdkOutboundCallAnalysisResponseSetReaction)
log.Printf("Read Outbound Call Analysis Response Set %s %s", d.Id(), *responseSet.Name)
return cc.CheckState(d)
})
}
// updateOutboundCallanalysisresponseset is used by the outbound_callanalysisresponseset resource to update an outbound callanalysisresponseset in Genesys Cloud
func updateOutboundCallanalysisresponseset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallanalysisresponsesetProxy(sdkConfig)
responseSet := getResponseSetFromResourceData(d)
log.Printf("Updating Outbound Call Analysis Response Set %s %s", *responseSet.Name, d.Id())
_, resp, err := proxy.updateOutboundCallanalysisresponseset(ctx, d.Id(), &responseSet)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound Call Analysis Response Set %s error: %s", *responseSet.Name, err), resp)
}
log.Printf("Updated Outbound Call Analysis Response Set %s %s", *responseSet.Name, d.Id())
return readOutboundCallanalysisresponseset(ctx, d, meta)
}
// deleteOutboundCallanalysisresponseset is used by the outbound_callanalysisresponseset resource to delete an outbound callanalysisresponseset from Genesys cloud
func deleteOutboundCallanalysisresponseset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCallanalysisresponsesetProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound Call Analysis Response Set")
resp, err := proxy.deleteOutboundCallanalysisresponseset(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound Call Analysis Response Set %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getOutboundCallanalysisresponsesetById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound Call Analysis Response Set deleted
log.Printf("Deleted Outbound Call Analysis Response Set %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Call Analysis Response Set %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Call Analysis Response Set %s still exists", d.Id()), resp))
})
}
package outbound_callanalysisresponseset
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_outbound_callanalysisresponseset_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the outbound_callanalysisresponseset resource.
3. The datasource schema definitions for the outbound_callanalysisresponseset datasource.
4. The resource exporter configuration for the outbound_callanalysisresponseset exporter.
*/
const resourceName = "genesyscloud_outbound_callanalysisresponseset"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceOutboundCallanalysisresponseset())
regInstance.RegisterDataSource(resourceName, DataSourceOutboundCallanalysisresponseset())
regInstance.RegisterExporter(resourceName, OutboundCallanalysisresponsesetExporter())
}
var (
reactionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`data`: {
Description: `Parameter for this reaction. For transfer_flow, this would be the outbound flow id.`,
Optional: true,
Type: schema.TypeString,
},
`name`: {
Description: `Name of the parameter for this reaction. For transfer_flow, this would be the outbound flow name.`,
Optional: true,
Type: schema.TypeString,
},
`reaction_type`: {
Description: `The reaction to take for a given call analysis result.`,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{`hangup`, `transfer`, `transfer_flow`, `play_file`}, false),
},
},
}
responseResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`callable_lineconnected`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`callable_person`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`callable_busy`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`callable_noanswer`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`callable_fax`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`callable_disconnect`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`callable_machine`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`callable_sit`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`uncallable_sit`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
`uncallable_notfound`: {
Computed: true,
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: reactionResource,
},
},
}
)
// ResourceOutboundCallanalysisresponseset registers the genesyscloud_outbound_callanalysisresponseset resource with Terraform
func ResourceOutboundCallanalysisresponseset() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud outbound Call Analysis Response Set`,
CreateContext: provider.CreateWithPooledClient(createOutboundCallanalysisresponseset),
ReadContext: provider.ReadWithPooledClient(readOutboundCallanalysisresponseset),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundCallanalysisresponseset),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundCallanalysisresponseset),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the Response Set.`,
Required: true,
Type: schema.TypeString,
},
`responses`: {
Description: `List of maps of disposition identifiers to reactions. Required if beep_detection_enabled = true.`,
Optional: true,
MaxItems: 1,
Type: schema.TypeList,
Elem: responseResource,
},
`beep_detection_enabled`: {
Description: `Whether to enable answering machine beep detection`,
Optional: true,
Default: false,
Type: schema.TypeBool,
},
},
}
}
// OutboundCallanalysisresponsesetExporter returns the resourceExporter object used to hold the genesyscloud_outbound_callanalysisresponseset exporter's config
func OutboundCallanalysisresponsesetExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthOutboundCallanalysisresponsesets),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"responses.callable_person.data": {RefType: "genesyscloud_flow"},
"responses.callable_machine.data": {RefType: "genesyscloud_flow"},
},
}
}
// DataSourceOutboundCallanalysisresponseset registers the genesyscloud_outbound_callanalysisresponseset data source
func DataSourceOutboundCallanalysisresponseset() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud outbound callanalysisresponseset data source. Select an outbound callanalysisresponseset by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundCallanalysisresponsesetRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: "Data source for Genesys Cloud Outbound Call Analysis Response Sets. Select a response set by name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package outbound_callanalysisresponseset
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
func getResponseSetFromResourceData(d *schema.ResourceData) platformclientv2.Responseset {
sdkResponseSet := platformclientv2.Responseset{
Name: platformclientv2.String(d.Get("name").(string)),
BeepDetectionEnabled: platformclientv2.Bool(d.Get("beep_detection_enabled").(bool)),
}
responses := d.Get("responses").([]interface{})
if responses != nil && len(responses) > 0 {
sdkResponseSet.Responses = buildSdkOutboundCallAnalysisResponseSetReaction(responses)
}
return sdkResponseSet
}
func buildSdkOutboundCallAnalysisResponseSetReaction(responses []interface{}) *map[string]platformclientv2.Reaction {
if len(responses) == 0 {
return nil
}
sdkResponses := map[string]platformclientv2.Reaction{}
if responsesMap, ok := responses[0].(map[string]interface{}); ok {
types := []string{
"callable_lineconnected",
"callable_person",
"callable_busy",
"callable_noanswer",
"callable_fax",
"callable_disconnect",
"callable_machine",
"callable_sit",
"uncallable_sit",
"uncallable_notfound",
}
for _, t := range types {
reactionSet := responsesMap[t].(*schema.Set).List()
if len(reactionSet) == 0 {
continue
}
if reactionMap, ok := reactionSet[0].(map[string]interface{}); ok {
sdkKey := "disposition.classification." + strings.ReplaceAll(t, "_", ".")
sdkResponses[sdkKey] = *buildSdkReaction(reactionMap)
}
}
}
return &sdkResponses
}
func buildSdkReaction(reactionMap map[string]interface{}) *platformclientv2.Reaction {
var sdkReaction platformclientv2.Reaction
resourcedata.BuildSDKStringValueIfNotNil(&sdkReaction.Name, reactionMap, "name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkReaction.Data, reactionMap, "data")
resourcedata.BuildSDKStringValueIfNotNil(&sdkReaction.ReactionType, reactionMap, "reaction_type")
return &sdkReaction
}
func flattenSdkOutboundCallAnalysisResponseSetReaction(responses *map[string]platformclientv2.Reaction) []interface{} {
if responses == nil {
return nil
}
responsesMap := make(map[string]interface{})
for key, val := range *responses {
schemaKey := strings.Replace(key, "disposition.classification.", "", -1)
schemaKey = strings.Replace(schemaKey, ".", "_", -1)
responsesMap[schemaKey] = flattenSdkReaction(val)
}
return []interface{}{responsesMap}
}
func flattenSdkReaction(sdkReaction platformclientv2.Reaction) *schema.Set {
var (
reactionMap = make(map[string]interface{})
reactionSet = schema.NewSet(schema.HashResource(reactionResource), []interface{}{})
)
if sdkReaction.Name != nil {
reactionMap["name"] = *sdkReaction.Name
}
if sdkReaction.Data != nil {
reactionMap["data"] = *sdkReaction.Data
}
reactionMap["reaction_type"] = *sdkReaction.ReactionType
reactionSet.Add(reactionMap)
return reactionSet
}
func GenerateOutboundCallAnalysisResponseSetResource(resourceId string, name string, beepDetectionEnabled string, responsesBlock string) string {
return fmt.Sprintf(`
resource "genesyscloud_outbound_callanalysisresponseset" "%s" {
name = "%s"
beep_detection_enabled = %s
%s
}
`, resourceId, name, beepDetectionEnabled, responsesBlock)
}
func GenerateCarsResponsesBlock(nestedBlocks ...string) string {
return fmt.Sprintf(`
responses {
%s
}
`, strings.Join(nestedBlocks, "\n"))
}
func GenerateCarsResponse(identifier string, reactionType string, name string, data string) string {
if name != "" {
name = fmt.Sprintf(`name = "%s"`, name)
}
if data != "" {
data = fmt.Sprintf(`data = "%s"`, data)
}
return fmt.Sprintf(`
%s {
reaction_type = "%s"
%s
%s
}
`, identifier, reactionType, name, data)
}
package outbound_campaign
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
)
/*
The data_source_genesyscloud_outbound_campaign.go contains the data source implementation
for the resource.
*/
// dataSourceOutboundCampaignRead retrieves by name the id in question
func dataSourceOutboundCampaignRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundCampaignProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
campaignId, retryable, resp, err := proxy.getOutboundCampaignIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error campaign %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No campaign found with name %s", name), resp))
}
d.SetId(campaignId)
return nil
})
}
package outbound_campaign
import (
"context"
"fmt"
"log"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_outbound_campaign_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundCampaignProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOutboundCampaignFunc func(ctx context.Context, p *outboundCampaignProxy, campaign *platformclientv2.Campaign) (*platformclientv2.Campaign, *platformclientv2.APIResponse, error)
type getAllOutboundCampaignFunc func(ctx context.Context, p *outboundCampaignProxy) (*[]platformclientv2.Campaign, *platformclientv2.APIResponse, error)
type getOutboundCampaignIdByNameFunc func(ctx context.Context, p *outboundCampaignProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getOutboundCampaignByIdFunc func(ctx context.Context, p *outboundCampaignProxy, id string) (campaign *platformclientv2.Campaign, response *platformclientv2.APIResponse, err error)
type updateOutboundCampaignFunc func(ctx context.Context, p *outboundCampaignProxy, id string, campaign *platformclientv2.Campaign) (*platformclientv2.Campaign, *platformclientv2.APIResponse, error)
type deleteOutboundCampaignFunc func(ctx context.Context, p *outboundCampaignProxy, id string) (response *platformclientv2.APIResponse, err error)
// outboundCampaignProxy contains all of the methods that call genesys cloud APIs.
type outboundCampaignProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundCampaignAttr createOutboundCampaignFunc
getAllOutboundCampaignAttr getAllOutboundCampaignFunc
getOutboundCampaignIdByNameAttr getOutboundCampaignIdByNameFunc
getOutboundCampaignByIdAttr getOutboundCampaignByIdFunc
updateOutboundCampaignAttr updateOutboundCampaignFunc
deleteOutboundCampaignAttr deleteOutboundCampaignFunc
campaignCache rc.CacheInterface[platformclientv2.Campaign]
}
// newOutboundCampaignProxy initializes the outbound campaign proxy with all of the data needed to communicate with Genesys Cloud
func newOutboundCampaignProxy(clientConfig *platformclientv2.Configuration) *outboundCampaignProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
campaignCache := rc.NewResourceCache[platformclientv2.Campaign]()
return &outboundCampaignProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundCampaignAttr: createOutboundCampaignFn,
getAllOutboundCampaignAttr: getAllOutboundCampaignFn,
getOutboundCampaignIdByNameAttr: getOutboundCampaignIdByNameFn,
getOutboundCampaignByIdAttr: getOutboundCampaignByIdFn,
updateOutboundCampaignAttr: updateOutboundCampaignFn,
deleteOutboundCampaignAttr: deleteOutboundCampaignFn,
campaignCache: campaignCache,
}
}
// getOutboundCampaignProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundCampaignProxy(clientConfig *platformclientv2.Configuration) *outboundCampaignProxy {
if internalProxy == nil {
internalProxy = newOutboundCampaignProxy(clientConfig)
}
return internalProxy
}
// createOutboundCampaign creates a Genesys Cloud outbound campaign
func (p *outboundCampaignProxy) createOutboundCampaign(ctx context.Context, outboundCampaign *platformclientv2.Campaign) (*platformclientv2.Campaign, *platformclientv2.APIResponse, error) {
return p.createOutboundCampaignAttr(ctx, p, outboundCampaign)
}
// getOutboundCampaign retrieves all Genesys Cloud outbound campaign
func (p *outboundCampaignProxy) getAllOutboundCampaign(ctx context.Context) (*[]platformclientv2.Campaign, *platformclientv2.APIResponse, error) {
return p.getAllOutboundCampaignAttr(ctx, p)
}
// getOutboundCampaignIdByName returns a single Genesys Cloud outbound campaign by a name
func (p *outboundCampaignProxy) getOutboundCampaignIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundCampaignIdByNameAttr(ctx, p, name)
}
// getOutboundCampaignById returns a single Genesys Cloud outbound campaign by Id
func (p *outboundCampaignProxy) getOutboundCampaignById(ctx context.Context, id string) (outboundCampaign *platformclientv2.Campaign, response *platformclientv2.APIResponse, err error) {
if campaign := rc.GetCacheItem(p.campaignCache, id); campaign != nil {
return campaign, nil, nil
}
return p.getOutboundCampaignByIdAttr(ctx, p, id)
}
// updateOutboundCampaign updates a Genesys Cloud outbound campaign
func (p *outboundCampaignProxy) updateOutboundCampaign(ctx context.Context, id string, outboundCampaign *platformclientv2.Campaign) (*platformclientv2.Campaign, *platformclientv2.APIResponse, error) {
return p.updateOutboundCampaignAttr(ctx, p, id, outboundCampaign)
}
// deleteOutboundCampaign deletes a Genesys Cloud outbound campaign by Id
func (p *outboundCampaignProxy) deleteOutboundCampaign(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundCampaignAttr(ctx, p, id)
}
// turnOffCampaign sets a campaign's campaign_status to 'off' before confirming the update using retry logic and get calls
func (p *outboundCampaignProxy) turnOffCampaign(ctx context.Context, campaignId string) diag.Diagnostics {
log.Printf("Reading Outbound Campaign %s", campaignId)
outboundCampaign, resp, getErr := p.getOutboundCampaignById(ctx, campaignId)
if getErr != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Campaign %s: %s", campaignId, getErr), resp)
}
log.Printf("Read Outbound Campaign %s", campaignId)
log.Printf("Updating campaign '%s' campaign_status to off", *outboundCampaign.Name)
if diagErr := updateOutboundCampaignStatus(ctx, campaignId, p, *outboundCampaign, "off"); diagErr != nil {
return diagErr
}
log.Printf("Updated campaign '%s'", *outboundCampaign.Name)
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
log.Printf("Reading Outbound Campaign %s to ensure campaign_status is 'off'", campaignId)
outboundCampaign, resp, getErr := p.getOutboundCampaignById(ctx, campaignId)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Campaign %s | error: %s", campaignId, getErr), resp))
}
log.Printf("Read Outbound Campaign %s", campaignId)
if *outboundCampaign.CampaignStatus == "on" {
time.Sleep(5 * time.Second)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("campaign %s campaign_status is still %s", campaignId, *outboundCampaign.CampaignStatus), resp))
}
// Success
return nil
})
}
// createOutboundCampaignFn is an implementation function for creating a Genesys Cloud outbound campaign
func createOutboundCampaignFn(_ context.Context, p *outboundCampaignProxy, outboundCampaign *platformclientv2.Campaign) (*platformclientv2.Campaign, *platformclientv2.APIResponse, error) {
campaign, resp, err := p.outboundApi.PostOutboundCampaigns(*outboundCampaign)
if err != nil {
return nil, resp, fmt.Errorf("failed to create campaign %s", err)
}
return campaign, resp, nil
}
// getAllOutboundCampaignFn is the implementation for retrieving all outbound campaign in Genesys Cloud
func getAllOutboundCampaignFn(_ context.Context, p *outboundCampaignProxy) (*[]platformclientv2.Campaign, *platformclientv2.APIResponse, error) {
var allCampaigns []platformclientv2.Campaign
const pageSize = 100
campaigns, resp, err := p.outboundApi.GetOutboundCampaigns(pageSize, 1, "", "", nil, "", "", "", "", "", nil, "", "")
if err != nil {
return nil, resp, fmt.Errorf("failed to get campaign: %s", err)
}
if campaigns.Entities == nil || len(*campaigns.Entities) == 0 {
return &allCampaigns, resp, nil
}
allCampaigns = append(allCampaigns, *campaigns.Entities...)
for pageNum := 2; pageNum <= *campaigns.PageCount; pageNum++ {
campaigns, resp, err := p.outboundApi.GetOutboundCampaigns(pageSize, pageNum, "", "", nil, "", "", "", "", "", nil, "", "")
if err != nil {
return nil, resp, fmt.Errorf("failed to get campaign: %s", err)
}
if campaigns.Entities == nil || len(*campaigns.Entities) == 0 {
break
}
allCampaigns = append(allCampaigns, *campaigns.Entities...)
}
for _, campaign := range allCampaigns {
rc.SetCache(p.campaignCache, *campaign.Id, campaign)
}
return &allCampaigns, resp, nil
}
// getOutboundCampaignIdByNameFn is an implementation of the function to get a Genesys Cloud outbound campaign by name
func getOutboundCampaignIdByNameFn(ctx context.Context, p *outboundCampaignProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
campaigns, resp, err := getAllOutboundCampaignFn(ctx, p)
if err != nil {
return "", false, resp, err
}
if campaigns == nil || len(*campaigns) == 0 {
return "", true, resp, fmt.Errorf("no campaigns found with name %s", name)
}
for _, campaign := range *campaigns {
if *campaign.Name == name {
log.Printf("Retrieved the campaign id %s by name %s", *campaign.Id, name)
return *campaign.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("unable to find campaign with name %s", name)
}
// getOutboundCampaignByIdFn is an implementation of the function to get a Genesys Cloud outbound campaign by Id
func getOutboundCampaignByIdFn(_ context.Context, p *outboundCampaignProxy, id string) (outboundCampaign *platformclientv2.Campaign, response *platformclientv2.APIResponse, err error) {
campaign, resp, err := p.outboundApi.GetOutboundCampaign(id)
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve campaign by id %s: %s", id, err)
}
return campaign, resp, nil
}
// updateOutboundCampaignFn is an implementation of the function to update a Genesys Cloud outbound campaign
func updateOutboundCampaignFn(ctx context.Context, p *outboundCampaignProxy, id string, outboundCampaign *platformclientv2.Campaign) (*platformclientv2.Campaign, *platformclientv2.APIResponse, error) {
campaign, resp, err := getOutboundCampaignByIdFn(ctx, p, id)
if err != nil {
return nil, resp, fmt.Errorf("failed to campaign by id %s: %s", id, err)
}
outboundCampaign.Version = campaign.Version
outboundCampaign, resp, err = p.outboundApi.PutOutboundCampaign(id, *outboundCampaign)
if err != nil {
return nil, resp, fmt.Errorf("failed to update campaign: %s", err)
}
return outboundCampaign, resp, nil
}
// deleteOutboundCampaignFn is an implementation function for deleting a Genesys Cloud outbound campaign
func deleteOutboundCampaignFn(_ context.Context, p *outboundCampaignProxy, id string) (response *platformclientv2.APIResponse, err error) {
_, resp, err := p.outboundApi.DeleteOutboundCampaign(id)
if err != nil {
return resp, fmt.Errorf("failed to delete campaign: %s", err)
}
rc.DeleteCacheItem(p.campaignCache, id)
return resp, nil
}
package outbound_campaign
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_outbound_campaign.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthOutboundCampaign retrieves all of the outbound campaign via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthOutboundCampaign(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getOutboundCampaignProxy(clientConfig)
campaigns, resp, err := proxy.getAllOutboundCampaign(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get campaigns error: %s", err), resp)
}
for _, campaign := range *campaigns {
// If a campaign is "stopping" during the export process we may encounter an error when we read the campaign later, and it will stop the export.
// We will give the campaign time to stop here and skip any that won't stop in time
if *campaign.CampaignStatus == "stopping" {
log.Println("Campaign is stopping")
// Retry to give the campaign time to turn off
err := util.WithRetries(ctx, 5*time.Minute, func() *retry.RetryError {
campaign, resp, getErr := proxy.getOutboundCampaignById(ctx, *campaign.Id)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Campaign %s during export | error: %s", *campaign.Id, getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Campaign %s: during export | error: %s", *campaign.Id, getErr), resp))
}
if *campaign.CampaignStatus == "stopping" {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Campaign %s didn't stop in time, unable to export", *campaign.Id), resp))
}
return nil
})
if err != nil {
log.Printf("%v", err)
continue
}
}
resources[*campaign.Id] = &resourceExporter.ResourceMeta{Name: *campaign.Name}
}
return resources, nil
}
// createOutboundCampaign is used by the outbound_campaign resource to create Genesys cloud outbound campaign
func createOutboundCampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
clientConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignProxy(clientConfig)
campaignStatus := d.Get("campaign_status").(string)
campaign := getOutboundCampaignFromResourceData(d)
// Create campaign
log.Printf("Creating Outbound Campaign %s", *campaign.Name)
outboundCampaign, resp, err := proxy.createOutboundCampaign(ctx, &campaign)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Campaign %s error: %s", *campaign.Name, err), resp)
}
d.SetId(*outboundCampaign.Id)
// Campaigns can be enabled after creation
if campaignStatus == "on" {
_ = d.Set("campaign_status", campaignStatus)
diagErr := updateOutboundCampaignStatus(ctx, d.Id(), proxy, *outboundCampaign, campaignStatus)
if diagErr != nil {
return diagErr
}
}
log.Printf("Created Outbound Campaign %s %s", *outboundCampaign.Name, *outboundCampaign.Id)
return readOutboundCampaign(ctx, d, meta)
}
// readOutboundCampaign is used by the outbound_campaign resource to read an outbound campaign from genesys cloud
func readOutboundCampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
clientConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignProxy(clientConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundCampaign(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Campaign %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
campaign, resp, getErr := proxy.getOutboundCampaignById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Campaign %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Campaign %s | error: %s", d.Id(), getErr), resp))
}
if *campaign.CampaignStatus == "stopping" {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Campaign still stopping %s", d.Id()), resp))
}
resourcedata.SetNillableValue(d, "name", campaign.Name)
resourcedata.SetNillableReference(d, "contact_list_id", campaign.ContactList)
resourcedata.SetNillableReference(d, "queue_id", campaign.Queue)
resourcedata.SetNillableValue(d, "dialing_mode", campaign.DialingMode)
resourcedata.SetNillableReference(d, "script_id", campaign.Script)
resourcedata.SetNillableReference(d, "edge_group_id", campaign.EdgeGroup)
resourcedata.SetNillableReference(d, "site_id", campaign.Site)
resourcedata.SetNillableValue(d, "campaign_status", campaign.CampaignStatus)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "phone_columns", campaign.PhoneColumns, flattenPhoneColumn)
resourcedata.SetNillableValue(d, "abandon_rate", campaign.AbandonRate)
if campaign.DncLists != nil {
_ = d.Set("dnc_list_ids", util.SdkDomainEntityRefArrToList(*campaign.DncLists))
}
resourcedata.SetNillableReference(d, "callable_time_set_id", campaign.CallableTimeSet)
resourcedata.SetNillableReference(d, "call_analysis_response_set_id", campaign.CallAnalysisResponseSet)
resourcedata.SetNillableValue(d, "caller_name", campaign.CallerName)
resourcedata.SetNillableValue(d, "caller_address", campaign.CallerAddress)
resourcedata.SetNillableValue(d, "outbound_line_count", campaign.OutboundLineCount)
if campaign.RuleSets != nil {
_ = d.Set("rule_set_ids", util.SdkDomainEntityRefArrToList(*campaign.RuleSets))
}
resourcedata.SetNillableValue(d, "skip_preview_disabled", campaign.SkipPreviewDisabled)
resourcedata.SetNillableValue(d, "preview_time_out_seconds", campaign.PreviewTimeOutSeconds)
resourcedata.SetNillableValue(d, "always_running", campaign.AlwaysRunning)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "contact_sorts", campaign.ContactSorts, flattenContactSorts)
resourcedata.SetNillableValue(d, "no_answer_timeout", campaign.NoAnswerTimeout)
resourcedata.SetNillableValue(d, "call_analysis_language", campaign.CallAnalysisLanguage)
resourcedata.SetNillableValue(d, "priority", campaign.Priority)
if campaign.ContactListFilters != nil {
_ = d.Set("contact_list_filter_ids", util.SdkDomainEntityRefArrToList(*campaign.ContactListFilters))
}
resourcedata.SetNillableReference(d, "division_id", campaign.Division)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "dynamic_contact_queueing_settings", campaign.DynamicContactQueueingSettings, flattenSettings)
log.Printf("Read Outbound Campaign %s %s", d.Id(), *campaign.Name)
return cc.CheckState(d)
})
}
// updateOutboundCampaign is used by the outbound_campaign resource to update an outbound campaign in Genesys Cloud
func updateOutboundCampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
clientConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignProxy(clientConfig)
campaignStatus := d.Get("campaign_status").(string)
campaign := getOutboundCampaignFromResourceData(d)
log.Printf("Updating Outbound Campaign %s", *campaign.Name)
campaignSdk, resp, err := proxy.updateOutboundCampaign(ctx, d.Id(), &campaign)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update campaign %s error: %s", *campaign.Name, err), resp)
}
// Check if Campaign Status needs updated
diagErr := updateOutboundCampaignStatus(ctx, d.Id(), proxy, *campaignSdk, campaignStatus)
if diagErr != nil {
return diagErr
}
log.Printf("Updated Outbound Campaign %s", *campaign.Name)
return readOutboundCampaign(ctx, d, meta)
}
// deleteOutboundCampaign is used by the outbound_campaign resource to delete an outbound campaign from Genesys cloud
func deleteOutboundCampaign(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
clientConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignProxy(clientConfig)
campaignStatus := d.Get("campaign_status").(string)
// Campaigns have to be turned off before they can be deleted
if campaignStatus == "on" {
currentCampaign, resp, err := proxy.getOutboundCampaignById(ctx, d.Id())
if err != nil {
log.Printf("failed to read campaign %s: %v %v", d.Id(), err, resp)
}
if *currentCampaign.CampaignStatus == "complete" {
log.Printf("Deleting campaign %s in 'complete' state", *currentCampaign.Id)
} else {
log.Printf("Turning off Outbound Campaign before deletion")
if diagErr := proxy.turnOffCampaign(ctx, d.Id()); diagErr != nil {
return diagErr
}
}
}
log.Printf("Deleting Outbound Campaign %s", d.Id())
resp, err := proxy.deleteOutboundCampaign(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete campaign %s error: %s", d.Id(), err), resp)
}
log.Printf("Deleted Outbound Campaign %s", d.Id())
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
log.Printf("Reading Outbound Campaign %s to confirm is has been deleted", d.Id())
_, resp, err := proxy.getOutboundCampaignById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound Campaign deleted
log.Printf("Deleted Outbound Campaign %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Outbound Campaign %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Campaign %s still exists", d.Id()), resp))
})
}
package outbound_campaign
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/outbound"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_outbound_campaign_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the outbound_campaign resource.
3. The datasource schema definitions for the outbound_campaign datasource.
4. The resource exporter configuration for the outbound_campaign exporter.
*/
const resourceName = "genesyscloud_outbound_campaign"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceOutboundCampaign())
regInstance.RegisterDataSource(resourceName, DataSourceOutboundCampaign())
regInstance.RegisterExporter(resourceName, OutboundCampaignExporter())
}
// ResourceOutboundCampaign registers the genesyscloud_outbound_campaign resource with Terraform
func ResourceOutboundCampaign() *schema.Resource {
outboundcampaignphonecolumnResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`column_name`: {
Description: `The name of the phone column.`,
Required: true,
Type: schema.TypeString,
},
},
}
return &schema.Resource{
Description: `Genesys Cloud outbound campaign`,
CreateContext: provider.CreateWithPooledClient(createOutboundCampaign),
ReadContext: provider.ReadWithPooledClient(readOutboundCampaign),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundCampaign),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundCampaign),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the Campaign.`,
Required: true,
Type: schema.TypeString,
},
`contact_list_id`: {
Description: `The ContactList for this Campaign to dial.`,
Required: true,
Type: schema.TypeString,
},
`queue_id`: {
Description: `The Queue for this Campaign to route calls to. Required for all dialing modes except agentless.`,
Optional: true,
Type: schema.TypeString,
},
`dialing_mode`: {
Description: `The strategy this Campaign will use for dialing.`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`agentless`, `preview`, `power`, `predictive`, `progressive`, `external`}, false),
},
`script_id`: {
Description: `The Script to be displayed to agents that are handling outbound calls. Required for all dialing modes except agentless.`,
Optional: true,
Type: schema.TypeString,
},
`edge_group_id`: {
Description: `The EdgeGroup that will place the calls. Required for all dialing modes except preview.`,
Optional: true,
Type: schema.TypeString,
},
`site_id`: {
Description: `The identifier of the site to be used for dialing; can be set in place of an edge group.`,
Optional: true,
Type: schema.TypeString,
},
`campaign_status`: {
Description: `The current status of the Campaign. A Campaign may be turned 'on' or 'off' (default). If this value is changed alongside other changes to the resource, a subsequent update will occur immediately afterwards to set the campaign status. This is due to behavioral requirements in the Genesys Cloud API.`,
Optional: true,
Type: schema.TypeString,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{`on`, `off`}, false),
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return (old == `complete` && new == `off`) || (old == `invalid` && new == `off`) || (old == `stopping` && new == `off` || old == `complete` && new == `on`)
},
},
`phone_columns`: {
Description: `The ContactPhoneNumberColumns on the ContactList that this Campaign should dial.`,
Required: true,
Type: schema.TypeList,
Elem: outboundcampaignphonecolumnResource,
},
`abandon_rate`: {
Description: `The targeted abandon rate percentage. Required for progressive, power, and predictive campaigns.`,
Optional: true,
Type: schema.TypeFloat,
},
`dnc_list_ids`: {
Description: `DncLists for this Campaign to check before placing a call.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`callable_time_set_id`: {
Description: `The callable time set for this campaign to check before placing a call.`,
Optional: true,
Type: schema.TypeString,
},
`call_analysis_response_set_id`: {
Description: `The call analysis response set to handle call analysis results from the edge. Required for all dialing modes except preview.`,
Optional: true,
Type: schema.TypeString,
},
`caller_name`: {
Description: `The caller id name to be displayed on the outbound call.`,
Required: true,
Type: schema.TypeString,
},
`caller_address`: {
Description: `The caller id phone number to be displayed on the outbound call.`,
Required: true,
Type: schema.TypeString,
},
`outbound_line_count`: {
Description: `The number of outbound lines to be concurrently dialed. Only applicable to non-preview campaigns; only required for agentless.`,
Optional: true,
Type: schema.TypeInt,
},
`rule_set_ids`: {
Description: `Rule sets to be applied while this campaign is dialing.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`skip_preview_disabled`: {
Description: `Whether or not agents can skip previews without placing a call. Only applicable for preview campaigns.`,
Optional: true,
Type: schema.TypeBool,
},
`preview_time_out_seconds`: {
Description: `The number of seconds before a call will be automatically placed on a preview. A value of 0 indicates no automatic placement of calls. Only applicable to preview campaigns.`,
Optional: true,
Type: schema.TypeInt,
},
`always_running`: {
Description: `Indicates (when true) that the campaign will remain on after contacts are depleted, allowing additional contacts to be appended/added to the contact list and processed by the still-running campaign. The campaign can still be turned off manually.`,
Optional: true,
Type: schema.TypeBool,
},
`contact_sorts`: {
Description: `The order in which to sort contacts for dialing, based on up to four columns.`,
Optional: true,
Type: schema.TypeList,
Elem: outbound.OutboundmessagingcampaigncontactsortResource,
},
`no_answer_timeout`: {
Description: `How long to wait before dispositioning a call as 'no-answer'. Default 30 seconds. Only applicable to non-preview campaigns.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`call_analysis_language`: {
Description: `The language the edge will use to analyze the call.`,
Optional: true,
Type: schema.TypeString,
},
`priority`: {
Description: `The priority of this campaign relative to other campaigns that are running on the same queue. 5 is the highest priority, 1 the lowest.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`contact_list_filter_ids`: {
Description: `Filter to apply to the contact list before dialing. Currently a campaign can only have one filter applied.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`division_id`: {
Description: `The division this campaign belongs to.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`dynamic_contact_queueing_settings`: {
Description: `Settings for dynamic queueing of contacts.`,
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"sort": {
Description: "Whether to sort contacts dynamically.",
Type: schema.TypeBool,
Required: true,
ForceNew: true,
},
},
},
},
},
}
}
// OutboundCampaignExporter returns the resourceExporter object used to hold the genesyscloud_outbound_campaign exporter's config
func OutboundCampaignExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthOutboundCampaign),
AllowZeroValues: []string{`preview_time_out_seconds`},
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
`contact_list_id`: {
RefType: "genesyscloud_outbound_contact_list",
},
`queue_id`: {
RefType: "genesyscloud_routing_queue",
},
`edge_group_id`: {
RefType: "genesyscloud_telephony_providers_edges_edge_group",
},
`site_id`: {
RefType: "genesyscloud_telephony_providers_edges_site",
},
`dnc_list_ids`: {
RefType: "genesyscloud_outbound_dnclist",
},
`call_analysis_response_set_id`: {
RefType: "genesyscloud_outbound_callanalysisresponseset",
},
`contact_list_filter_ids`: {
RefType: "genesyscloud_outbound_contactlistfilter",
},
`division_id`: {
RefType: "genesyscloud_auth_division",
},
`rule_set_ids`: {
RefType: "genesyscloud_outbound_ruleset",
},
`callable_time_set_id`: {
RefType: "genesyscloud_outbound_callabletimeset",
},
`script_id`: {
RefType: "genesyscloud_script",
},
},
CustomAttributeResolver: map[string]*resourceExporter.RefAttrCustomResolver{
"campaign_status": {ResolverFunc: resourceExporter.CampaignStatusResolver},
"script_id": {
ResolveToDataSourceFunc: resourceExporter.OutboundCampaignAgentScriptResolver,
},
},
}
}
// DataSourceOutboundCampaign registers the genesyscloud_outbound_campaign data source
func DataSourceOutboundCampaign() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud outbound campaign data source. Select an outbound campaign by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundCampaignRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `outbound campaign name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package outbound_campaign
import (
"context"
"fmt"
"log"
"strconv"
gcloud "terraform-provider-genesyscloud/genesyscloud"
"terraform-provider-genesyscloud/genesyscloud/architect_flow"
obResponseSet "terraform-provider-genesyscloud/genesyscloud/outbound_callanalysisresponseset"
obContactList "terraform-provider-genesyscloud/genesyscloud/outbound_contact_list"
obContactListFilter "terraform-provider-genesyscloud/genesyscloud/outbound_contactlistfilter"
obDnclist "terraform-provider-genesyscloud/genesyscloud/outbound_dnclist"
routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_outbound_campaign_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
func getOutboundCampaignFromResourceData(d *schema.ResourceData) platformclientv2.Campaign {
abandonRate := d.Get("abandon_rate").(float64)
outboundLineCount := d.Get("outbound_line_count").(int)
skipPreviewDisabled := d.Get("skip_preview_disabled").(bool)
previewTimeOutSeconds := d.Get("preview_time_out_seconds").(int)
alwaysRunning := d.Get("always_running").(bool)
noAnswerTimeout := d.Get("no_answer_timeout").(int)
callAnalysisLanguage := d.Get("call_analysis_language").(string)
priority := d.Get("priority").(int)
campaign := platformclientv2.Campaign{
Name: platformclientv2.String(d.Get("name").(string)),
DialingMode: platformclientv2.String(d.Get("dialing_mode").(string)),
CallerAddress: platformclientv2.String(d.Get("caller_address").(string)),
CallerName: platformclientv2.String(d.Get("caller_name").(string)),
CampaignStatus: platformclientv2.String("off"),
ContactList: util.BuildSdkDomainEntityRef(d, "contact_list_id"),
Queue: util.BuildSdkDomainEntityRef(d, "queue_id"),
Script: util.BuildSdkDomainEntityRef(d, "script_id"),
EdgeGroup: util.BuildSdkDomainEntityRef(d, "edge_group_id"),
Site: util.BuildSdkDomainEntityRef(d, "site_id"),
PhoneColumns: buildPhoneColumns(d.Get("phone_columns").([]interface{})),
DncLists: util.BuildSdkDomainEntityRefArr(d, "dnc_list_ids"),
CallableTimeSet: util.BuildSdkDomainEntityRef(d, "callable_time_set_id"),
CallAnalysisResponseSet: util.BuildSdkDomainEntityRef(d, "call_analysis_response_set_id"),
RuleSets: util.BuildSdkDomainEntityRefArr(d, "rule_set_ids"),
SkipPreviewDisabled: &skipPreviewDisabled,
AlwaysRunning: &alwaysRunning,
ContactSorts: buildContactSorts(d.Get("contact_sorts").([]interface{})),
ContactListFilters: util.BuildSdkDomainEntityRefArr(d, "contact_list_filter_ids"),
Division: util.BuildSdkDomainEntityRef(d, "division_id"),
DynamicContactQueueingSettings: buildSettings(d.Get("dynamic_contact_queueing_settings").([]interface{})),
}
if abandonRate != 0 {
campaign.AbandonRate = &abandonRate
}
if outboundLineCount != 0 {
campaign.OutboundLineCount = &outboundLineCount
}
if previewTimeOutSeconds != 0 {
campaign.PreviewTimeOutSeconds = &previewTimeOutSeconds
}
if noAnswerTimeout != 0 {
campaign.NoAnswerTimeout = &noAnswerTimeout
}
if callAnalysisLanguage != "" {
campaign.CallAnalysisLanguage = &callAnalysisLanguage
}
if priority != 0 {
campaign.Priority = &priority
}
return campaign
}
func updateOutboundCampaignStatus(ctx context.Context, campaignId string, proxy *outboundCampaignProxy, campaign platformclientv2.Campaign, newCampaignStatus string) diag.Diagnostics {
if newCampaignStatus == "" {
return nil
}
// Campaign status can only go from ON -> OFF or OFF, COMPLETE, INVALID, ETC -> ON
if (*campaign.CampaignStatus == "on" && newCampaignStatus == "off") || newCampaignStatus == "on" {
campaign.CampaignStatus = &newCampaignStatus
log.Printf("Updating Outbound Campaign %s status to %s", *campaign.Name, newCampaignStatus)
_, resp, err := proxy.updateOutboundCampaign(ctx, campaignId, &campaign)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound Campaign %s error: %s", *campaign.Name, err), resp)
}
}
return nil
}
func buildPhoneColumns(phonecolumns []interface{}) *[]platformclientv2.Phonecolumn {
if len(phonecolumns) == 0 {
return nil
}
phonecolumnSlice := make([]platformclientv2.Phonecolumn, 0)
for _, phonecolumn := range phonecolumns {
var sdkPhonecolumn platformclientv2.Phonecolumn
phonecolumnMap, ok := phonecolumn.(map[string]interface{})
if !ok {
continue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkPhonecolumn.ColumnName, phonecolumnMap, "column_name")
phonecolumnSlice = append(phonecolumnSlice, sdkPhonecolumn)
}
return &phonecolumnSlice
}
func buildSettings(settings []interface{}) *platformclientv2.Dynamiccontactqueueingsettings {
if settings == nil || len(settings) < 1 {
return nil
}
var sdkDcqSettings platformclientv2.Dynamiccontactqueueingsettings
dcqSetting, ok := settings[0].(map[string]interface{})
if !ok {
return nil
}
if sort, ok := dcqSetting["sort"].(bool); ok {
sdkDcqSettings.Sort = &sort
}
return &sdkDcqSettings
}
func buildContactSorts(contactSortList []interface{}) *[]platformclientv2.Contactsort {
if len(contactSortList) == 0 {
return nil
}
sdkContactsortSlice := make([]platformclientv2.Contactsort, 0)
for _, configcontactsort := range contactSortList {
var sdkContactsort platformclientv2.Contactsort
contactsortMap := configcontactsort.(map[string]interface{})
resourcedata.BuildSDKStringValueIfNotNil(&sdkContactsort.FieldName, contactsortMap, "field_name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkContactsort.Direction, contactsortMap, "direction")
sdkContactsort.Numeric = platformclientv2.Bool(contactsortMap["numeric"].(bool))
sdkContactsortSlice = append(sdkContactsortSlice, sdkContactsort)
}
return &sdkContactsortSlice
}
func flattenSettings(settings *platformclientv2.Dynamiccontactqueueingsettings) []interface{} {
settingsMap := make(map[string]interface{}, 0)
settingsMap["sort"] = *settings.Sort
return []interface{}{settingsMap}
}
func flattenPhoneColumn(phonecolumns *[]platformclientv2.Phonecolumn) []interface{} {
if len(*phonecolumns) == 0 {
return nil
}
phonecolumnList := make([]interface{}, 0)
for _, phonecolumn := range *phonecolumns {
phonecolumnMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(phonecolumnMap, "column_name", phonecolumn.ColumnName)
phonecolumnList = append(phonecolumnList, phonecolumnMap)
}
return phonecolumnList
}
func flattenContactSorts(contactSorts *[]platformclientv2.Contactsort) []interface{} {
if len(*contactSorts) == 0 {
return nil
}
contactSortList := make([]interface{}, 0)
for _, contactSort := range *contactSorts {
contactSortMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(contactSortMap, "field_name", contactSort.FieldName)
resourcedata.SetMapValueIfNotNil(contactSortMap, "direction", contactSort.Direction)
resourcedata.SetMapValueIfNotNil(contactSortMap, "numeric", contactSort.Numeric)
contactSortList = append(contactSortList, contactSortMap)
}
return contactSortList
}
func GenerateOutboundCampaignBasic(resourceId string,
name string,
contactListResourceId string,
siteResourceId string,
emergencyNumber string,
carResourceId string,
campaignStatus string,
outboundFlowFilePath string,
flowResourceId string,
flowName string,
divisionName,
locationResourceId string,
wrapupcodeResourceId string) string {
referencedResources := GenerateReferencedResourcesForOutboundCampaignTests(
contactListResourceId,
"",
"",
carResourceId,
outboundFlowFilePath,
flowResourceId,
flowName,
"",
siteResourceId,
emergencyNumber,
"",
"",
divisionName,
locationResourceId,
wrapupcodeResourceId,
)
return fmt.Sprintf(`
resource "genesyscloud_outbound_campaign" "%s" {
name = "%s"
dialing_mode = "agentless"
caller_name = "Test Name"
caller_address = "+353371111111"
outbound_line_count = 2
campaign_status = %s
contact_list_id = genesyscloud_outbound_contact_list.%s.id
site_id = genesyscloud_telephony_providers_edges_site.%s.id
call_analysis_response_set_id = genesyscloud_outbound_callanalysisresponseset.%s.id
phone_columns {
column_name = "Cell"
}
}
%s
`, resourceId, name, campaignStatus, contactListResourceId, siteResourceId, carResourceId, referencedResources)
}
func GenerateReferencedResourcesForOutboundCampaignTests(
contactListResourceId string,
dncListResourceId string,
queueResourceId string,
carResourceId string,
outboundFlowFilePath string,
flowResourceId string,
flowName string,
clfResourceId string,
siteId string,
emergencyNumber string,
ruleSetId string,
callableTimeSetResourceId string,
divisionName string,
locationResourceId string,
wrapUpCodeResourceId string,
) string {
var (
contactList string
dncList string
queue string
callAnalysisResponseSet string
contactListFilter string
site string
ruleSet string
callableTimeSet string
)
if contactListResourceId != "" {
contactList = obContactList.GenerateOutboundContactList(
contactListResourceId,
"terraform contact list "+uuid.NewString(),
util.NullValue,
strconv.Quote("Cell"),
[]string{strconv.Quote("Cell")},
[]string{strconv.Quote("Cell"), strconv.Quote("Home"), strconv.Quote("zipcode")},
util.FalseValue,
util.NullValue,
util.NullValue,
obContactList.GeneratePhoneColumnsBlock("Cell", "cell", strconv.Quote("Cell")),
obContactList.GeneratePhoneColumnsBlock("Home", "home", strconv.Quote("Home")))
}
if dncListResourceId != "" {
dncList = obDnclist.GenerateOutboundDncListBasic(dncListResourceId, "tf dnc list "+uuid.NewString())
}
if queueResourceId != "" {
queue = routingQueue.GenerateRoutingQueueResourceBasic(queueResourceId, "tf test queue "+uuid.NewString())
}
if carResourceId != "" {
if outboundFlowFilePath != "" {
callAnalysisResponseSet = gcloud.GenerateRoutingWrapupcodeResource(
wrapUpCodeResourceId,
"wrapupcode "+uuid.NewString(),
) + architect_flow.GenerateFlowResource(
flowResourceId,
outboundFlowFilePath,
"",
false,
util.GenerateSubstitutionsMap(map[string]string{
"flow_name": flowName,
"home_division_name": divisionName,
"contact_list_name": "${genesyscloud_outbound_contact_list." + contactListResourceId + ".name}",
"wrapup_code_name": "${genesyscloud_routing_wrapupcode." + wrapUpCodeResourceId + ".name}",
}),
) + obResponseSet.GenerateOutboundCallAnalysisResponseSetResource(
carResourceId,
"tf test car "+uuid.NewString(),
util.FalseValue,
obResponseSet.GenerateCarsResponsesBlock(
obResponseSet.GenerateCarsResponse(
"callable_person",
"transfer_flow",
flowName,
"${genesyscloud_flow."+flowResourceId+".id}",
),
))
} else {
callAnalysisResponseSet = obResponseSet.GenerateOutboundCallAnalysisResponseSetResource(
carResourceId,
"tf test car "+uuid.NewString(),
util.TrueValue,
obResponseSet.GenerateCarsResponsesBlock(
obResponseSet.GenerateCarsResponse(
"callable_machine",
"transfer",
"",
"",
),
),
)
}
}
if clfResourceId != "" {
contactListFilter = obContactListFilter.GenerateOutboundContactListFilter(
clfResourceId,
"tf test clf "+uuid.NewString(),
"genesyscloud_outbound_contact_list."+contactListResourceId+".id",
"",
obContactListFilter.GenerateOutboundContactListFilterClause(
"",
obContactListFilter.GenerateOutboundContactListFilterPredicates(
"Cell",
"alphabetic",
"EQUALS",
"+12345123456",
"",
"",
),
),
)
}
if siteId != "" {
siteName := "site " + uuid.NewString()
locationName := "location " + uuid.NewString()
site = fmt.Sprintf(`
resource "genesyscloud_location" "%s" {
name = "%s"
notes = "HQ1"
path = []
emergency_number {
number = "%s"
type = null
}
address {
street1 = "7601 Interactive Way"
city = "Indianapolis"
state = "IN"
country = "US"
zip_code = "46278"
}
}
resource "genesyscloud_telephony_providers_edges_site" "%s" {
name = "%s"
description = "TestAccResourceSite description 1"
location_id = genesyscloud_location.%s.id
media_model = "Cloud"
media_regions_use_latency_based = false
}
`, locationResourceId, locationName, emergencyNumber, siteId, siteName, locationResourceId)
}
if ruleSetId != "" {
ruleSetName := "ruleset " + uuid.NewString()
ruleSet = fmt.Sprintf(`
resource "genesyscloud_outbound_ruleset" "%s" {
name = "%s"
contact_list_id = genesyscloud_outbound_contact_list.%s.id
}
`, ruleSetId, ruleSetName, contactListResourceId)
}
if callableTimeSetResourceId != "" {
callableTimeSetName := "test time set " + uuid.NewString()
callableTimeSet = fmt.Sprintf(`
resource "genesyscloud_outbound_callabletimeset" "%s"{
name = "%s"
callable_times {
time_zone_id = "Africa/Abidjan"
time_slots {
start_time = "07:00:00"
stop_time = "18:00:00"
day = 3
}
}
}
`, callableTimeSetResourceId, callableTimeSetName)
}
return fmt.Sprintf(`
%s
%s
%s
%s
%s
%s
%s
%s
`, contactList, dncList, queue, callAnalysisResponseSet, contactListFilter, site, ruleSet, callableTimeSet)
}
package outbound_campaignrule
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceOutboundCampaignruleRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
outboundAPI := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
name := d.Get("name").(string)
// Query campaign rule by name. Retry in case search has not yet indexed the campaign rule.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageNum = 1
const pageSize = 100
campaignRules, resp, getErr := outboundAPI.GetOutboundCampaignrules(pageSize, pageNum, true, "", name, "", "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting campaign rule %s | error: %s", name, getErr), resp))
}
if campaignRules.Entities == nil || len(*campaignRules.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no campaign rules found with name %s", name), resp))
}
campaignRule := (*campaignRules.Entities)[0]
d.SetId(*campaignRule.Id)
return nil
})
}
package outbound_campaignrule
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_outbound_campaignrule_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundCampaignruleProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOutboundCampaignruleFunc func(ctx context.Context, p *outboundCampaignruleProxy, campaignRule *platformclientv2.Campaignrule) (*platformclientv2.Campaignrule, *platformclientv2.APIResponse, error)
type getAllOutboundCampaignruleFunc func(ctx context.Context, p *outboundCampaignruleProxy) (*[]platformclientv2.Campaignrule, *platformclientv2.APIResponse, error)
type getOutboundCampaignruleIdByNameFunc func(ctx context.Context, p *outboundCampaignruleProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getOutboundCampaignruleByIdFunc func(ctx context.Context, p *outboundCampaignruleProxy, id string) (campaignRule *platformclientv2.Campaignrule, response *platformclientv2.APIResponse, err error)
type updateOutboundCampaignruleFunc func(ctx context.Context, p *outboundCampaignruleProxy, id string, campaignRule *platformclientv2.Campaignrule) (*platformclientv2.Campaignrule, *platformclientv2.APIResponse, error)
type deleteOutboundCampaignruleFunc func(ctx context.Context, p *outboundCampaignruleProxy, id string) (response *platformclientv2.APIResponse, err error)
// outboundCampaignruleProxy contains all of the methods that call genesys cloud APIs.
type outboundCampaignruleProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundCampaignruleAttr createOutboundCampaignruleFunc
getAllOutboundCampaignruleAttr getAllOutboundCampaignruleFunc
getOutboundCampaignruleIdByNameAttr getOutboundCampaignruleIdByNameFunc
getOutboundCampaignruleByIdAttr getOutboundCampaignruleByIdFunc
updateOutboundCampaignruleAttr updateOutboundCampaignruleFunc
deleteOutboundCampaignruleAttr deleteOutboundCampaignruleFunc
}
// newOutboundCampaignruleProxy initializes the outbound campaignrule proxy with all of the data needed to communicate with Genesys Cloud
func newOutboundCampaignruleProxy(clientConfig *platformclientv2.Configuration) *outboundCampaignruleProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundCampaignruleProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundCampaignruleAttr: createOutboundCampaignruleFn,
getAllOutboundCampaignruleAttr: getAllOutboundCampaignruleFn,
getOutboundCampaignruleIdByNameAttr: getOutboundCampaignruleIdByNameFn,
getOutboundCampaignruleByIdAttr: getOutboundCampaignruleByIdFn,
updateOutboundCampaignruleAttr: updateOutboundCampaignruleFn,
deleteOutboundCampaignruleAttr: deleteOutboundCampaignruleFn,
}
}
// getOutboundCampaignruleProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundCampaignruleProxy(clientConfig *platformclientv2.Configuration) *outboundCampaignruleProxy {
if internalProxy == nil {
internalProxy = newOutboundCampaignruleProxy(clientConfig)
}
return internalProxy
}
// createOutboundCampaignrule creates a Genesys Cloud outbound campaignrule
func (p *outboundCampaignruleProxy) createOutboundCampaignrule(ctx context.Context, outboundCampaignrule *platformclientv2.Campaignrule) (*platformclientv2.Campaignrule, *platformclientv2.APIResponse, error) {
return p.createOutboundCampaignruleAttr(ctx, p, outboundCampaignrule)
}
// getOutboundCampaignrule retrieves all Genesys Cloud outbound campaignrule
func (p *outboundCampaignruleProxy) getAllOutboundCampaignrule(ctx context.Context) (*[]platformclientv2.Campaignrule, *platformclientv2.APIResponse, error) {
return p.getAllOutboundCampaignruleAttr(ctx, p)
}
// getOutboundCampaignruleIdByName returns a single Genesys Cloud outbound campaignrule by a name
func (p *outboundCampaignruleProxy) getOutboundCampaignruleIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundCampaignruleIdByNameAttr(ctx, p, name)
}
// getOutboundCampaignruleById returns a single Genesys Cloud outbound campaignrule by Id
func (p *outboundCampaignruleProxy) getOutboundCampaignruleById(ctx context.Context, id string) (outboundCampaignrule *platformclientv2.Campaignrule, response *platformclientv2.APIResponse, err error) {
return p.getOutboundCampaignruleByIdAttr(ctx, p, id)
}
// updateOutboundCampaignrule updates a Genesys Cloud outbound campaignrule
func (p *outboundCampaignruleProxy) updateOutboundCampaignrule(ctx context.Context, id string, outboundCampaignrule *platformclientv2.Campaignrule) (*platformclientv2.Campaignrule, *platformclientv2.APIResponse, error) {
return p.updateOutboundCampaignruleAttr(ctx, p, id, outboundCampaignrule)
}
// deleteOutboundCampaignrule deletes a Genesys Cloud outbound campaignrule by Id
func (p *outboundCampaignruleProxy) deleteOutboundCampaignrule(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundCampaignruleAttr(ctx, p, id)
}
// createOutboundCampaignruleFn is an implementation function for creating a Genesys Cloud outbound campaignrule
func createOutboundCampaignruleFn(ctx context.Context, p *outboundCampaignruleProxy, outboundCampaignrule *platformclientv2.Campaignrule) (*platformclientv2.Campaignrule, *platformclientv2.APIResponse, error) {
rule, resp, err := p.outboundApi.PostOutboundCampaignrules(*outboundCampaignrule)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create campaign rule %s", err)
}
return rule, resp, nil
}
// getAllOutboundCampaignruleFn is the implementation for retrieving all outbound campaignrule in Genesys Cloud
func getAllOutboundCampaignruleFn(ctx context.Context, p *outboundCampaignruleProxy) (*[]platformclientv2.Campaignrule, *platformclientv2.APIResponse, error) {
var allCampaignRules []platformclientv2.Campaignrule
const pageSize = 100
campaignRules, resp, err := p.outboundApi.GetOutboundCampaignrules(pageSize, 1, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get campaign rules: %v", err)
}
if campaignRules.Entities == nil || len(*campaignRules.Entities) == 0 {
return &allCampaignRules, resp, nil
}
for _, campaignRule := range *campaignRules.Entities {
allCampaignRules = append(allCampaignRules, campaignRule)
}
for pageNum := 2; pageNum <= *campaignRules.PageCount; pageNum++ {
campaignRules, resp, err := p.outboundApi.GetOutboundCampaignrules(pageSize, pageNum, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get campaign rules: %v", err)
}
if campaignRules.Entities == nil || len(*campaignRules.Entities) == 0 {
break
}
for _, campaignRule := range *campaignRules.Entities {
allCampaignRules = append(allCampaignRules, campaignRule)
}
}
return &allCampaignRules, resp, nil
}
// getOutboundCampaignruleIdByNameFn is an implementation of the function to get a Genesys Cloud outbound campaignrule by name
func getOutboundCampaignruleIdByNameFn(ctx context.Context, p *outboundCampaignruleProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
campaignRules, resp, err := p.outboundApi.GetOutboundCampaignrules(100, 1, true, "", name, "", "")
if err != nil {
return "", false, resp, err
}
if campaignRules.Entities == nil || len(*campaignRules.Entities) == 0 {
return "", true, resp, fmt.Errorf("No outbound campaignrule with name %s", name)
}
for _, campaignRule := range *campaignRules.Entities {
if *campaignRule.Name == name {
log.Printf("Retrieved the outbound capaign rule id %s by name %s", *campaignRule.Id, name)
return *campaignRule.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find outbound campaign rule with name %s", name)
}
// getOutboundCampaignruleByIdFn is an implementation of the function to get a Genesys Cloud outbound campaignrule by Id
func getOutboundCampaignruleByIdFn(ctx context.Context, p *outboundCampaignruleProxy, id string) (outboundCampaignrule *platformclientv2.Campaignrule, response *platformclientv2.APIResponse, err error) {
rule, resp, err := p.outboundApi.GetOutboundCampaignrule(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve campaign rule by id %s: %s", id, err)
}
return rule, resp, nil
}
// updateOutboundCampaignruleFn is an implementation of the function to update a Genesys Cloud outbound campaignrule
func updateOutboundCampaignruleFn(ctx context.Context, p *outboundCampaignruleProxy, id string, outboundCampaignrule *platformclientv2.Campaignrule) (*platformclientv2.Campaignrule, *platformclientv2.APIResponse, error) {
rule, resp, err := getOutboundCampaignruleByIdFn(ctx, p, id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to campaign rule by id %s: %s", id, err)
}
outboundCampaignrule.Version = rule.Version
campaignRule, resp, err := p.outboundApi.PutOutboundCampaignrule(id, *outboundCampaignrule)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update campaign rule: %s", err)
}
return campaignRule, resp, nil
}
// deleteOutboundCampaignruleFn is an implementation function for deleting a Genesys Cloud outbound campaignrule
func deleteOutboundCampaignruleFn(ctx context.Context, p *outboundCampaignruleProxy, id string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.outboundApi.DeleteOutboundCampaignrule(id)
if err != nil {
return resp, fmt.Errorf("Failed to delete campaign rule: %s", err)
}
return resp, nil
}
package outbound_campaignrule
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllAuthCampaignRules(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getOutboundCampaignruleProxy(clientConfig)
campaignRules, resp, err := proxy.getAllOutboundCampaignrule(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get outbound campaign rules error: %s", err), resp)
}
for _, campaignRule := range *campaignRules {
resources[*campaignRule.Id] = &resourceExporter.ResourceMeta{Name: *campaignRule.Name}
}
return resources, nil
}
func createOutboundCampaignRule(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignruleProxy(sdkConfig)
rule := getCampaignruleFromResourceData(d)
log.Printf("Creating Outbound Campaign Rule %s", *rule.Name)
outboundCampaignRule, resp, err := proxy.createOutboundCampaignrule(ctx, &rule)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Campaign Rule %s error: %s", *rule.Name, err), resp)
}
d.SetId(*outboundCampaignRule.Id)
log.Printf("Created Outbound Campaign Rule %s %s", *outboundCampaignRule.Name, *outboundCampaignRule.Id)
enabled := d.Get("enabled").(bool)
// Campaign rules can be enabled after creation
if enabled {
d.Set("enabled", enabled)
diag := updateOutboundCampaignRule(ctx, d, meta)
if diag != nil {
return diag
}
}
return readOutboundCampaignRule(ctx, d, meta)
}
func updateOutboundCampaignRule(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignruleProxy(sdkConfig)
enabled := d.Get("enabled").(bool)
rule := getCampaignruleFromResourceData(d)
if enabled {
rule.Enabled = platformclientv2.Bool(true)
}
log.Printf("Updating Outbound Campaign Rule %s", *rule.Name)
_, resp, err := proxy.updateOutboundCampaignrule(ctx, d.Id(), &rule)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update campaign rule %s error: %s", *rule.Name, err), resp)
}
log.Printf("Updated Outbound Campaign Rule %s", *rule.Name)
return readOutboundCampaignRule(ctx, d, meta)
}
func readOutboundCampaignRule(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignruleProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundCampaignrule(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Campaign Rule %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
campaignRule, resp, getErr := proxy.getOutboundCampaignruleById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Campaign Rule %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Campaign Rule %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", campaignRule.Name)
if campaignRule.CampaignRuleEntities != nil {
d.Set("campaign_rule_entities", flattenCampaignRuleEntities(campaignRule.CampaignRuleEntities))
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "campaign_rule_conditions", campaignRule.CampaignRuleConditions, flattenCampaignRuleConditions)
if campaignRule.CampaignRuleActions != nil {
d.Set("campaign_rule_actions", flattenCampaignRuleAction(campaignRule.CampaignRuleActions, flattenCampaignRuleActionEntities))
} else {
d.Set("campaign_rule_actions", nil)
}
resourcedata.SetNillableValue(d, "match_any_conditions", campaignRule.MatchAnyConditions)
resourcedata.SetNillableValue(d, "enabled", campaignRule.Enabled)
log.Printf("Read Outbound Campaign Rule %s %s", d.Id(), *campaignRule.Name)
return cc.CheckState(d)
})
}
func deleteOutboundCampaignRule(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundCampaignruleProxy(sdkConfig)
ruleEnabled := d.Get("enabled").(bool)
if ruleEnabled {
// Have to disable rule before we can delete
log.Printf("Disabling Outbound Campaign Rule")
d.Set("enabled", false)
rule, resp, err := proxy.getOutboundCampaignruleById(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get Outbound campaign rule %s error: %s", d.Id(), err), resp)
}
rule.Enabled = platformclientv2.Bool(false)
_, resp, err = proxy.updateOutboundCampaignrule(ctx, d.Id(), rule)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to disable outbound campagin rule %s error: %s", d.Id(), err), resp)
}
}
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound Campaign Rule")
resp, err := proxy.deleteOutboundCampaignrule(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete outbound campaign rule %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getOutboundCampaignruleById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound Campaign Rule deleted
log.Printf("Deleted Outbound Campaign Rule %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Campaign Rule %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Campaign Rule %s still exists", d.Id()), resp))
})
}
package outbound_campaignrule
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_outbound_campaignrule_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the outbound_campaignrule resource.
3. The datasource schema definitions for the outbound_campaignrule datasource.
4. The resource exporter configuration for the outbound_campaignrule exporter.
*/
const resourceName = "genesyscloud_outbound_campaignrule"
var (
outboundCampaignRuleEntities = &schema.Resource{
Schema: map[string]*schema.Schema{
`campaign_ids`: outboundCampaignRuleEntityCampaignRuleId,
`sequence_ids`: outboundCampaignRuleEntitySequenceRuleId,
},
}
outboundCampaignRuleEntityCampaignRuleId = &schema.Schema{
Description: `The list of campaigns for a CampaignRule to monitor. Required if the CampaignRule has any conditions that run on a campaign. Changing the outboundCampaignRuleEntityCampaignRuleId attribute will cause the outbound_campaignrule object to be dropped and recreated with a new ID.`,
Optional: true,
ForceNew: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
}
outboundCampaignRuleEntitySequenceRuleId = &schema.Schema{
Description: `The list of sequences for a CampaignRule to monitor. Required if the CampaignRule has any conditions that run on a sequence. Changing the outboundCampaignRuleEntitySequenceRuleId attribute will cause the outbound_campaignrule object to be dropped and recreated with a new ID.`,
Optional: true,
ForceNew: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
}
outboundCampaignRuleActionEntities = &schema.Resource{
Schema: map[string]*schema.Schema{
`campaign_ids`: outboundCampaignRuleEntityCampaignRuleId,
`sequence_ids`: outboundCampaignRuleEntitySequenceRuleId,
`use_triggering_entity`: {
Description: `If true, the CampaignRuleAction will apply to the same entity that triggered the CampaignRuleCondition.`,
Optional: true,
Type: schema.TypeBool,
Default: false,
},
},
}
)
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceOutboundCampaignrule())
regInstance.RegisterDataSource(resourceName, DataSourceOutboundCampaignrule())
regInstance.RegisterExporter(resourceName, OutboundCampaignruleExporter())
}
// ResourceOutboundCampaignrule registers the genesyscloud_outbound_campaignrule resource with Terraform
func ResourceOutboundCampaignrule() *schema.Resource {
campaignRuleParameters := &schema.Resource{
Schema: map[string]*schema.Schema{
`operator`: {
Description: `The operator for comparison. Required for a CampaignRuleCondition.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"equals", "greaterThan", "greaterThanEqualTo", "lessThan", "lessThanEqualTo"}, true),
},
`value`: {
Description: `The value for comparison. Required for a CampaignRuleCondition.`,
Optional: true,
Type: schema.TypeString,
},
`priority`: {
Description: `The priority to set a campaign to (1 | 2 | 3 | 4 | 5). Required for the 'setCampaignPriority' action.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"1", "2", "3", "4", "5"}, true),
},
`dialing_mode`: {
Description: `The dialing mode to set a campaign to. Required for the 'setCampaignDialingMode' action (agentless | preview | power | predictive | progressive | external).`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"agentless", "preview", "power", "predictive", "progressive", "external"}, true),
},
},
}
outboundCampaignRuleCondition := &schema.Resource{
Schema: map[string]*schema.Schema{
`id`: {
Description: `The ID of the CampaignRuleCondition.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`parameters`: {
Description: `The parameters for the CampaignRuleCondition.`,
Required: true,
Type: schema.TypeSet,
Elem: campaignRuleParameters,
},
`condition_type`: {
Description: `The type of condition to evaluate (campaignProgress | campaignAgents).`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"campaignProgress", "campaignAgents"}, true),
},
},
}
outboundCampaignRuleAction := &schema.Resource{
Schema: map[string]*schema.Schema{
`id`: {
Description: `The ID of the CampaignRuleAction.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`parameters`: {
Description: `The parameters for the CampaignRuleAction. Required for certain actionTypes.`,
Optional: true,
Type: schema.TypeSet,
Elem: campaignRuleParameters,
},
`action_type`: {
Description: `The action to take on the campaignRuleActionEntities
(turnOnCampaign | turnOffCampaign | turnOnSequence | turnOffSequence | setCampaignPriority | recycleCampaign | setCampaignDialingMode).`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"turnOnCampaign", "turnOffCampaign", "turnOnSequence", "turnOffSequence", "setCampaignPriority", "recycleCampaign", "setCampaignDialingMode"}, true),
},
`campaign_rule_action_entities`: {
Description: `The list of entities that this action will apply to.`,
Required: true,
Type: schema.TypeSet,
Elem: outboundCampaignRuleActionEntities,
},
},
}
return &schema.Resource{
Description: `Genesys Cloud outbound campaign rule`,
CreateContext: provider.CreateWithPooledClient(createOutboundCampaignRule),
ReadContext: provider.ReadWithPooledClient(readOutboundCampaignRule),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundCampaignRule),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundCampaignRule),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the campaign rule.`,
Required: true,
Type: schema.TypeString,
},
`campaign_rule_entities`: {
Description: `The list of entities that this campaign rule monitors.`,
Required: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: outboundCampaignRuleEntities,
},
`campaign_rule_conditions`: {
Description: `The list of conditions that are evaluated on the entities.`,
Required: true,
MinItems: 1,
Type: schema.TypeList,
Elem: outboundCampaignRuleCondition,
},
`campaign_rule_actions`: {
Description: `The list of actions that are executed if the conditions are satisfied.`,
Required: true,
Type: schema.TypeList,
Elem: outboundCampaignRuleAction,
},
`match_any_conditions`: {
Description: `Whether actions are executed if any condition is met, or only when all conditions are met.`,
Optional: true,
Default: false,
Type: schema.TypeBool,
},
`enabled`: {
Description: `Whether or not this campaign rule is currently enabled.`,
Optional: true,
Default: false,
Type: schema.TypeBool,
},
},
}
}
// OutboundCampaignruleExporter returns the resourceExporter object used to hold the genesyscloud_outbound_campaignrule exporter's config
func OutboundCampaignruleExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthCampaignRules),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
`campaign_rule_actions.campaign_rule_action_entities.campaign_ids`: {
RefType: "genesyscloud_outbound_campaign",
},
`campaign_rule_actions.campaign_rule_action_entities.sequence_ids`: {
RefType: "genesyscloud_outbound_sequence",
},
`campaign_rule_entities.campaign_ids`: {
RefType: "genesyscloud_outbound_campaign",
},
`campaign_rule_entities.sequence_ids`: {
RefType: "genesyscloud_outbound_sequence",
},
},
}
}
// DataSourceOutboundCampaignrule registers the genesyscloud_outbound_campaignrule data source
func DataSourceOutboundCampaignrule() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud outbound campaign rule data source. Select a campaign rule by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundCampaignruleRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: "Campaign Rule name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package outbound_campaignrule
import (
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getCampaignruleFromResourceData(d *schema.ResourceData) platformclientv2.Campaignrule {
matchAnyConditions := d.Get("match_any_conditions").(bool)
campaignRule := platformclientv2.Campaignrule{
Name: platformclientv2.String(d.Get("name").(string)),
Enabled: platformclientv2.Bool(false), // All campaign rules have to be created in an "off" state to start out with
CampaignRuleEntities: buildCampaignRuleEntities(d.Get("campaign_rule_entities").(*schema.Set)),
CampaignRuleConditions: buildCampaignRuleConditions(d.Get("campaign_rule_conditions").([]interface{})),
CampaignRuleActions: buildCampaignRuleAction(d.Get("campaign_rule_actions").([]interface{})),
MatchAnyConditions: &matchAnyConditions,
}
return campaignRule
}
func buildCampaignRuleEntities(entities *schema.Set) *platformclientv2.Campaignruleentities {
if entities == nil {
return nil
}
var campaignRuleEntities platformclientv2.Campaignruleentities
campaignRuleEntitiesList := entities.List()
if len(campaignRuleEntitiesList) <= 0 {
return &campaignRuleEntities
}
campaignRuleEntitiesMap := campaignRuleEntitiesList[0].(map[string]interface{})
if campaigns := campaignRuleEntitiesMap["campaign_ids"].([]interface{}); campaigns != nil {
campaignRuleEntities.Campaigns = util.BuildSdkDomainEntityRefArrFromArr(campaigns)
}
if sequences := campaignRuleEntitiesMap["sequence_ids"].([]interface{}); sequences != nil {
campaignRuleEntities.Sequences = util.BuildSdkDomainEntityRefArrFromArr(sequences)
}
return &campaignRuleEntities
}
func buildCampaignRuleConditions(campaignRuleConditions []interface{}) *[]platformclientv2.Campaignrulecondition {
var campaignRuleConditionSlice []platformclientv2.Campaignrulecondition
for _, campaignRuleCondition := range campaignRuleConditions {
sdkCondition := platformclientv2.Campaignrulecondition{}
conditionMap := campaignRuleCondition.(map[string]interface{})
sdkCondition.Parameters = buildCampaignRuleParameters(conditionMap["parameters"].(*schema.Set))
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.Id, conditionMap, "id")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.ConditionType, conditionMap, "condition_type")
campaignRuleConditionSlice = append(campaignRuleConditionSlice, sdkCondition)
}
return &campaignRuleConditionSlice
}
func buildCampaignRuleAction(campaignRuleActions []interface{}) *[]platformclientv2.Campaignruleaction {
var campaignRuleActionSlice []platformclientv2.Campaignruleaction
for _, campaignRuleAction := range campaignRuleActions {
var sdkCampaignRuleAction platformclientv2.Campaignruleaction
actionMap := campaignRuleAction.(map[string]interface{})
resourcedata.BuildSDKStringValueIfNotNil(&sdkCampaignRuleAction.Id, actionMap, "id")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCampaignRuleAction.ActionType, actionMap, "action_type")
sdkCampaignRuleAction.Parameters = buildCampaignRuleParameters(actionMap["parameters"].(*schema.Set))
sdkCampaignRuleAction.CampaignRuleActionEntities = buildCampaignRuleActionEntities(actionMap["campaign_rule_action_entities"].(*schema.Set))
campaignRuleActionSlice = append(campaignRuleActionSlice, sdkCampaignRuleAction)
}
return &campaignRuleActionSlice
}
func buildCampaignRuleParameters(set *schema.Set) *platformclientv2.Campaignruleparameters {
var sdkCampaignRuleParameters platformclientv2.Campaignruleparameters
paramsList := set.List()
if len(paramsList) <= 0 {
return &sdkCampaignRuleParameters
}
paramsMap := paramsList[0].(map[string]interface{})
resourcedata.BuildSDKStringValueIfNotNil(&sdkCampaignRuleParameters.Operator, paramsMap, "operator")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCampaignRuleParameters.Value, paramsMap, "value")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCampaignRuleParameters.Priority, paramsMap, "priority")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCampaignRuleParameters.DialingMode, paramsMap, "dialing_mode")
return &sdkCampaignRuleParameters
}
func buildCampaignRuleActionEntities(set *schema.Set) *platformclientv2.Campaignruleactionentities {
var (
sdkCampaignRuleActionEntities platformclientv2.Campaignruleactionentities
entities = set.List()
)
if len(entities) <= 0 {
return &sdkCampaignRuleActionEntities
}
entitiesMap := entities[0].(map[string]interface{})
sdkCampaignRuleActionEntities.UseTriggeringEntity = platformclientv2.Bool(entitiesMap["use_triggering_entity"].(bool))
if campaignIds := entitiesMap["campaign_ids"].([]interface{}); campaignIds != nil {
sdkCampaignRuleActionEntities.Campaigns = util.BuildSdkDomainEntityRefArrFromArr(campaignIds)
}
if sequenceIds := entitiesMap["sequence_ids"].([]interface{}); sequenceIds != nil {
sdkCampaignRuleActionEntities.Sequences = util.BuildSdkDomainEntityRefArrFromArr(sequenceIds)
}
return &sdkCampaignRuleActionEntities
}
func flattenCampaignRuleEntities(campaignRuleEntities *platformclientv2.Campaignruleentities) *schema.Set {
var (
campaignRuleEntitiesSet = schema.NewSet(schema.HashResource(outboundCampaignRuleEntities), []interface{}{})
campaignRuleEntitiesMap = make(map[string]interface{})
// had to change from []string to []interface{}
campaigns []interface{}
sequences []interface{}
)
if campaignRuleEntities == nil {
return nil
}
if campaignRuleEntities.Campaigns != nil {
for _, v := range *campaignRuleEntities.Campaigns {
campaigns = append(campaigns, *v.Id)
}
}
if campaignRuleEntities.Sequences != nil {
for _, v := range *campaignRuleEntities.Sequences {
sequences = append(sequences, *v.Id)
}
}
campaignRuleEntitiesMap["campaign_ids"] = campaigns
campaignRuleEntitiesMap["sequence_ids"] = sequences
campaignRuleEntitiesSet.Add(campaignRuleEntitiesMap)
return campaignRuleEntitiesSet
}
func flattenCampaignRuleConditions(campaignRuleConditions *[]platformclientv2.Campaignrulecondition) []interface{} {
if campaignRuleConditions == nil || len(*campaignRuleConditions) == 0 {
return nil
}
var ruleConditionList []interface{}
for _, currentSdkCondition := range *campaignRuleConditions {
campaignRuleConditionsMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(campaignRuleConditionsMap, "id", currentSdkCondition.Id)
resourcedata.SetMapValueIfNotNil(campaignRuleConditionsMap, "condition_type", currentSdkCondition.ConditionType)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(campaignRuleConditionsMap, "parameters", currentSdkCondition.Parameters, flattenRuleParameters)
ruleConditionList = append(ruleConditionList, campaignRuleConditionsMap)
}
return ruleConditionList
}
func flattenCampaignRuleAction[T any](campaignRuleActions *[]platformclientv2.Campaignruleaction, actionEntitiesFunc func(*platformclientv2.Campaignruleactionentities) T) []interface{} {
if campaignRuleActions == nil {
return nil
}
var ruleActionsList []interface{}
for _, currentAction := range *campaignRuleActions {
actionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(actionMap, "id", currentAction.Id)
resourcedata.SetMapValueIfNotNil(actionMap, "action_type", currentAction.ActionType)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(actionMap, "parameters", currentAction.Parameters, flattenRuleParameters)
if currentAction.CampaignRuleActionEntities != nil {
actionMap["campaign_rule_action_entities"] = actionEntitiesFunc(currentAction.CampaignRuleActionEntities)
}
ruleActionsList = append(ruleActionsList, actionMap)
}
return ruleActionsList
}
func flattenCampaignRuleActionEntities(sdkActionEntity *platformclientv2.Campaignruleactionentities) *schema.Set {
var (
campaigns []interface{}
sequences []interface{}
entitiesSet = schema.NewSet(schema.HashResource(outboundCampaignRuleActionEntities), []interface{}{})
entitiesMap = make(map[string]interface{})
)
if sdkActionEntity == nil {
return nil
}
if sdkActionEntity.Campaigns != nil {
for _, campaign := range *sdkActionEntity.Campaigns {
campaigns = append(campaigns, *campaign.Id)
}
}
if sdkActionEntity.Sequences != nil {
for _, sequence := range *sdkActionEntity.Sequences {
sequences = append(sequences, *sequence.Id)
}
}
entitiesMap["campaign_ids"] = campaigns
entitiesMap["sequence_ids"] = sequences
entitiesMap["use_triggering_entity"] = *sdkActionEntity.UseTriggeringEntity
entitiesSet.Add(entitiesMap)
return entitiesSet
}
func flattenRuleParameters(params *platformclientv2.Campaignruleparameters) []interface{} {
paramsMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(paramsMap, "operator", params.Operator)
resourcedata.SetMapValueIfNotNil(paramsMap, "value", params.Value)
resourcedata.SetMapValueIfNotNil(paramsMap, "priority", params.Priority)
resourcedata.SetMapValueIfNotNil(paramsMap, "dialing_mode", params.DialingMode)
return []interface{}{paramsMap}
}
package outbound_contact_list
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func DataSourceOutboundContactList() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Outbound Contact Lists. Select a contact list by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundContactListRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Contact List name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceOutboundContactListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
outboundAPI := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
const pageNum = 1
const pageSize = 100
contactLists, resp, getErr := outboundAPI.GetOutboundContactlists(false, false, pageSize, pageNum, true, "", name, []string{""}, []string{""}, "", "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting contact list %s | error: %s", name, getErr), resp))
}
if contactLists.Entities == nil || len(*contactLists.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no contact lists found with name %s", name), resp))
}
contactList := (*contactLists.Entities)[0]
d.SetId(*contactList.Id)
return nil
})
}
package outbound_contact_list
import (
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterDataSource("genesyscloud_outbound_contact_list", DataSourceOutboundContactList())
regInstance.RegisterResource("genesyscloud_outbound_contact_list", ResourceOutboundContactList())
regInstance.RegisterExporter("genesyscloud_outbound_contact_list", OutboundContactListExporter())
}
package outbound_contact_list
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
const (
resourceName = "genesyscloud_outbound_contactlist"
)
var (
outboundContactListContactPhoneNumberColumnResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`column_name`: {
Description: `The name of the phone column.`,
Required: true,
Type: schema.TypeString,
},
`type`: {
Description: `Indicates the type of the phone column. For example, 'cell' or 'home'.`,
Required: true,
Type: schema.TypeString,
},
`callable_time_column`: {
Description: `A column that indicates the timezone to use for a given contact when checking callable times. Not allowed if 'automaticTimeZoneMapping' is set to true.`,
Optional: true,
Type: schema.TypeString,
},
},
}
outboundContactListEmailColumnResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`column_name`: {
Description: `The name of the email column.`,
Required: true,
Type: schema.TypeString,
},
`type`: {
Description: `Indicates the type of the email column. For example, 'work' or 'personal'.`,
Required: true,
Type: schema.TypeString,
},
`contactable_time_column`: {
Description: `A column that indicates the timezone to use for a given contact when checking contactable times.`,
Optional: true,
Type: schema.TypeString,
},
},
}
outboundContactListColumnDataTypeSpecification = &schema.Resource{
Schema: map[string]*schema.Schema{
`column_name`: {
Description: `The column name of a column selected for dynamic queueing.`,
Required: true,
Type: schema.TypeString,
},
`column_data_type`: {
Description: `The data type of the column selected for dynamic queueing (TEXT, NUMERIC or TIMESTAMP)`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"TEXT", "NUMERIC", "TIMESTAMP"}, false),
},
`min`: {
Description: `The minimum length of the numeric column selected for dynamic queueing.`,
Optional: true,
Type: schema.TypeInt,
},
`max`: {
Description: `The maximum length of the numeric column selected for dynamic queueing.`,
Optional: true,
Type: schema.TypeInt,
},
`max_length`: {
Description: `The maximum length of the text column selected for dynamic queueing.`,
Required: true,
Type: schema.TypeInt,
},
},
}
)
func getAllOutboundContactLists(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
outboundAPI := platformclientv2.NewOutboundApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
contactListConfigs, resp, getErr := outboundAPI.GetOutboundContactlists(false, false, pageSize, pageNum, true, "", "", []string{}, []string{}, "", "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of contact list configs error: %s", getErr), resp)
}
if contactListConfigs.Entities == nil || len(*contactListConfigs.Entities) == 0 {
break
}
for _, contactListConfig := range *contactListConfigs.Entities {
resources[*contactListConfig.Id] = &resourceExporter.ResourceMeta{Name: *contactListConfig.Name}
}
}
return resources, nil
}
func OutboundContactListExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundContactLists),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"attempt_limit_id": {RefType: "genesyscloud_outbound_attempt_limit"},
"division_id": {RefType: "genesyscloud_auth_division"},
},
}
}
func ResourceOutboundContactList() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Outbound Contact List`,
CreateContext: provider.CreateWithPooledClient(createOutboundContactList),
ReadContext: provider.ReadWithPooledClient(readOutboundContactList),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundContactList),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundContactList),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name for the contact list.`,
Required: true,
Type: schema.TypeString,
},
`division_id`: {
Description: `The division this entity belongs to.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`column_names`: {
Description: `The names of the contact data columns. Changing the column_names attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`,
Required: true,
ForceNew: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`phone_columns`: {
Description: `Indicates which columns are phone numbers. Changing the phone_columns attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID. Required if email_columns is empty`,
Optional: true,
ForceNew: true,
Type: schema.TypeSet,
Elem: outboundContactListContactPhoneNumberColumnResource,
},
`email_columns`: {
Description: `Indicates which columns are email addresses. Changing the email_columns attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID. Required if phone_columns is empty`,
Optional: true,
ForceNew: true,
Type: schema.TypeSet,
Elem: outboundContactListEmailColumnResource,
},
`preview_mode_column_name`: {
Description: `A column to check if a contact should always be dialed in preview mode.`,
Optional: true,
Type: schema.TypeString,
},
`preview_mode_accepted_values`: {
Description: `The values in the previewModeColumnName column that indicate a contact should always be dialed in preview mode.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`attempt_limit_id`: {
Description: `Attempt Limit for this ContactList.`,
Optional: true,
Type: schema.TypeString,
},
`automatic_time_zone_mapping`: {
Description: `Indicates if automatic time zone mapping is to be used for this ContactList. Changing the automatic_time_zone_mappings attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`,
Optional: true,
ForceNew: true,
Type: schema.TypeBool,
},
`zip_code_column_name`: {
Description: `The name of contact list column containing the zip code for use with automatic time zone mapping. Only allowed if 'automaticTimeZoneMapping' is set to true. Changing the zip_code_column_name attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`,
Optional: true,
ForceNew: true,
Type: schema.TypeString,
},
`column_data_type_specifications`: {
Description: `The settings of the columns selected for dynamic queueing. If updated, the contact list is dropped and recreated with a new ID`,
Optional: true,
ForceNew: true,
Type: schema.TypeList,
Elem: outboundContactListColumnDataTypeSpecification,
},
},
}
}
func createOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{}))
previewModeColumnName := d.Get("preview_mode_column_name").(string)
previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{}))
automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool)
zipCodeColumnName := d.Get("zip_code_column_name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
sdkContactList := platformclientv2.Contactlist{
Division: util.BuildSdkDomainEntityRef(d, "division_id"),
ColumnNames: &columnNames,
PhoneColumns: buildSdkOutboundContactListContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)),
EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)),
PreviewModeAcceptedValues: &previewModeAcceptedValues,
AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"),
AutomaticTimeZoneMapping: &automaticTimeZoneMapping,
ColumnDataTypeSpecifications: buildSdkOutboundContactListColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})),
}
if name != "" {
sdkContactList.Name = &name
}
if previewModeColumnName != "" {
sdkContactList.PreviewModeColumnName = &previewModeColumnName
}
if zipCodeColumnName != "" {
sdkContactList.ZipCodeColumnName = &zipCodeColumnName
}
log.Printf("Creating Outbound Contact List %s", name)
outboundContactList, resp, err := outboundApi.PostOutboundContactlists(sdkContactList)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Contact List %s error: %s", name, err), resp)
}
d.SetId(*outboundContactList.Id)
log.Printf("Created Outbound Contact List %s %s", name, *outboundContactList.Id)
return readOutboundContactList(ctx, d, meta)
}
func updateOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{}))
previewModeColumnName := d.Get("preview_mode_column_name").(string)
previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{}))
automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool)
zipCodeColumnName := d.Get("zip_code_column_name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
sdkContactList := platformclientv2.Contactlist{
Division: util.BuildSdkDomainEntityRef(d, "division_id"),
ColumnNames: &columnNames,
PhoneColumns: buildSdkOutboundContactListContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)),
EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)),
PreviewModeAcceptedValues: &previewModeAcceptedValues,
AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"),
AutomaticTimeZoneMapping: &automaticTimeZoneMapping,
ColumnDataTypeSpecifications: buildSdkOutboundContactListColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})),
}
if name != "" {
sdkContactList.Name = &name
}
if previewModeColumnName != "" {
sdkContactList.PreviewModeColumnName = &previewModeColumnName
}
if zipCodeColumnName != "" {
sdkContactList.ZipCodeColumnName = &zipCodeColumnName
}
log.Printf("Updating Outbound Contact List %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current Outbound Contact list version
outboundContactList, resp, getErr := outboundApi.GetOutboundContactlist(d.Id(), false, false)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Contact List %s error: %s", d.Id(), getErr), resp)
}
sdkContactList.Version = outboundContactList.Version
outboundContactList, resp, updateErr := outboundApi.PutOutboundContactlist(d.Id(), sdkContactList)
if updateErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound contact list %s error: %s", name, updateErr), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Outbound Contact List %s", name)
return readOutboundContactList(ctx, d, meta)
}
func readOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundContactList(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Contact List %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkContactList, resp, getErr := outboundApi.GetOutboundContactlist(d.Id(), false, false)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List %s | error: %s", d.Id(), getErr), resp))
}
if sdkContactList.Name != nil {
_ = d.Set("name", *sdkContactList.Name)
}
if sdkContactList.Division != nil && sdkContactList.Division.Id != nil {
_ = d.Set("division_id", *sdkContactList.Division.Id)
}
if sdkContactList.ColumnNames != nil {
var columnNames []string
for _, name := range *sdkContactList.ColumnNames {
columnNames = append(columnNames, name)
}
_ = d.Set("column_names", columnNames)
}
if sdkContactList.PhoneColumns != nil {
_ = d.Set("phone_columns", flattenSdkOutboundContactListContactPhoneNumberColumnSlice(*sdkContactList.PhoneColumns))
}
if sdkContactList.EmailColumns != nil {
_ = d.Set("email_columns", flattenSdkOutboundContactListContactEmailAddressColumnSlice(*sdkContactList.EmailColumns))
}
if sdkContactList.PreviewModeColumnName != nil {
_ = d.Set("preview_mode_column_name", *sdkContactList.PreviewModeColumnName)
}
if sdkContactList.PreviewModeAcceptedValues != nil {
var acceptedValues []string
for _, val := range *sdkContactList.PreviewModeAcceptedValues {
acceptedValues = append(acceptedValues, val)
}
_ = d.Set("preview_mode_accepted_values", acceptedValues)
}
if sdkContactList.AttemptLimits != nil && sdkContactList.AttemptLimits.Id != nil {
_ = d.Set("attempt_limit_id", *sdkContactList.AttemptLimits.Id)
}
if sdkContactList.AutomaticTimeZoneMapping != nil {
_ = d.Set("automatic_time_zone_mapping", *sdkContactList.AutomaticTimeZoneMapping)
}
if sdkContactList.ZipCodeColumnName != nil {
_ = d.Set("zip_code_column_name", *sdkContactList.ZipCodeColumnName)
}
if sdkContactList.ColumnDataTypeSpecifications != nil {
_ = d.Set("column_data_type_specifications", flattenSdkOutboundContactListColumnDataTypeSpecifications(*sdkContactList.ColumnDataTypeSpecifications))
}
log.Printf("Read Outbound Contact List %s %s", d.Id(), *sdkContactList.Name)
return cc.CheckState(d)
})
}
func deleteOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound Contact List")
resp, err := outboundApi.DeleteOutboundContactlist(d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound Contact List %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := outboundApi.GetOutboundContactlist(d.Id(), false, false)
if err != nil {
if util.IsStatus404(resp) {
// Outbound Contact List deleted
log.Printf("Deleted Outbound Contact List %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Contact List %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Contact List %s still exists", d.Id()), resp))
})
}
func buildSdkOutboundContactListContactPhoneNumberColumnSlice(contactPhoneNumberColumn *schema.Set) *[]platformclientv2.Contactphonenumbercolumn {
if contactPhoneNumberColumn == nil {
return nil
}
sdkContactPhoneNumberColumnSlice := make([]platformclientv2.Contactphonenumbercolumn, 0)
contactPhoneNumberColumnList := contactPhoneNumberColumn.List()
for _, configPhoneColumn := range contactPhoneNumberColumnList {
var sdkContactPhoneNumberColumn platformclientv2.Contactphonenumbercolumn
contactPhoneNumberColumnMap := configPhoneColumn.(map[string]interface{})
if columnName := contactPhoneNumberColumnMap["column_name"].(string); columnName != "" {
sdkContactPhoneNumberColumn.ColumnName = &columnName
}
if varType := contactPhoneNumberColumnMap["type"].(string); varType != "" {
sdkContactPhoneNumberColumn.VarType = &varType
}
if callableTimeColumn := contactPhoneNumberColumnMap["callable_time_column"].(string); callableTimeColumn != "" {
sdkContactPhoneNumberColumn.CallableTimeColumn = &callableTimeColumn
}
sdkContactPhoneNumberColumnSlice = append(sdkContactPhoneNumberColumnSlice, sdkContactPhoneNumberColumn)
}
return &sdkContactPhoneNumberColumnSlice
}
func flattenSdkOutboundContactListContactPhoneNumberColumnSlice(contactPhoneNumberColumns []platformclientv2.Contactphonenumbercolumn) *schema.Set {
if len(contactPhoneNumberColumns) == 0 {
return nil
}
contactPhoneNumberColumnSet := schema.NewSet(schema.HashResource(outboundContactListContactPhoneNumberColumnResource), []interface{}{})
for _, contactPhoneNumberColumn := range contactPhoneNumberColumns {
contactPhoneNumberColumnMap := make(map[string]interface{})
if contactPhoneNumberColumn.ColumnName != nil {
contactPhoneNumberColumnMap["column_name"] = *contactPhoneNumberColumn.ColumnName
}
if contactPhoneNumberColumn.VarType != nil {
contactPhoneNumberColumnMap["type"] = *contactPhoneNumberColumn.VarType
}
if contactPhoneNumberColumn.CallableTimeColumn != nil {
contactPhoneNumberColumnMap["callable_time_column"] = *contactPhoneNumberColumn.CallableTimeColumn
}
contactPhoneNumberColumnSet.Add(contactPhoneNumberColumnMap)
}
return contactPhoneNumberColumnSet
}
func buildSdkOutboundContactListContactEmailAddressColumnSlice(contactEmailAddressColumn *schema.Set) *[]platformclientv2.Emailcolumn {
if contactEmailAddressColumn == nil {
return nil
}
sdkContactEmailAddressColumnSlice := make([]platformclientv2.Emailcolumn, 0)
contactEmailAddressColumnList := contactEmailAddressColumn.List()
for _, configEmailColumn := range contactEmailAddressColumnList {
var sdkContactEmailAddressColumn platformclientv2.Emailcolumn
contactEmailAddressColumnMap := configEmailColumn.(map[string]interface{})
if columnName := contactEmailAddressColumnMap["column_name"].(string); columnName != "" {
sdkContactEmailAddressColumn.ColumnName = &columnName
}
if varType := contactEmailAddressColumnMap["type"].(string); varType != "" {
sdkContactEmailAddressColumn.VarType = &varType
}
if contactableTimeColumn := contactEmailAddressColumnMap["contactable_time_column"].(string); contactableTimeColumn != "" {
sdkContactEmailAddressColumn.ContactableTimeColumn = &contactableTimeColumn
}
sdkContactEmailAddressColumnSlice = append(sdkContactEmailAddressColumnSlice, sdkContactEmailAddressColumn)
}
return &sdkContactEmailAddressColumnSlice
}
func flattenSdkOutboundContactListContactEmailAddressColumnSlice(contactEmailAddressColumns []platformclientv2.Emailcolumn) *schema.Set {
if len(contactEmailAddressColumns) == 0 {
return nil
}
contactEmailAddressColumnSet := schema.NewSet(schema.HashResource(outboundContactListEmailColumnResource), []interface{}{})
for _, contactEmailAddressColumn := range contactEmailAddressColumns {
contactEmailAddressColumnMap := make(map[string]interface{})
if contactEmailAddressColumn.ColumnName != nil {
contactEmailAddressColumnMap["column_name"] = *contactEmailAddressColumn.ColumnName
}
if contactEmailAddressColumn.VarType != nil {
contactEmailAddressColumnMap["type"] = *contactEmailAddressColumn.VarType
}
if contactEmailAddressColumn.ContactableTimeColumn != nil {
contactEmailAddressColumnMap["contactable_time_column"] = *contactEmailAddressColumn.ContactableTimeColumn
}
contactEmailAddressColumnSet.Add(contactEmailAddressColumnMap)
}
return contactEmailAddressColumnSet
}
func buildSdkOutboundContactListColumnDataTypeSpecifications(columnDataTypeSpecifications []interface{}) *[]platformclientv2.Columndatatypespecification {
if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) < 1 {
return nil
}
sdkColumnDataTypeSpecificationsSlice := make([]platformclientv2.Columndatatypespecification, 0)
for _, spec := range columnDataTypeSpecifications {
if specMap, ok := spec.(map[string]interface{}); ok {
var sdkColumnDataTypeSpecification platformclientv2.Columndatatypespecification
if columnNameStr, ok := specMap["column_name"].(string); ok {
sdkColumnDataTypeSpecification.ColumnName = &columnNameStr
}
if columnDataTypeStr, ok := specMap["column_data_type"].(string); ok && columnDataTypeStr != "" {
sdkColumnDataTypeSpecification.ColumnDataType = &columnDataTypeStr
}
if minInt, ok := specMap["min"].(int); ok {
sdkColumnDataTypeSpecification.Min = &minInt
}
if maxInt, ok := specMap["max"].(int); ok {
sdkColumnDataTypeSpecification.Max = &maxInt
}
if maxLengthInt, ok := specMap["max_length"].(int); ok {
sdkColumnDataTypeSpecification.MaxLength = &maxLengthInt
}
sdkColumnDataTypeSpecificationsSlice = append(sdkColumnDataTypeSpecificationsSlice, sdkColumnDataTypeSpecification)
}
}
return &sdkColumnDataTypeSpecificationsSlice
}
func flattenSdkOutboundContactListColumnDataTypeSpecifications(columnDataTypeSpecifications []platformclientv2.Columndatatypespecification) []interface{} {
if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) == 0 {
return nil
}
columnDataTypeSpecificationsSlice := make([]interface{}, 0)
for _, s := range columnDataTypeSpecifications {
columnDataTypeSpecification := make(map[string]interface{})
columnDataTypeSpecification["column_name"] = *s.ColumnName
if s.ColumnDataType != nil {
columnDataTypeSpecification["column_data_type"] = *s.ColumnDataType
}
if s.Min != nil {
columnDataTypeSpecification["min"] = *s.Min
}
if s.Max != nil {
columnDataTypeSpecification["max"] = *s.Max
}
if s.MaxLength != nil {
columnDataTypeSpecification["max_length"] = *s.MaxLength
}
columnDataTypeSpecificationsSlice = append(columnDataTypeSpecificationsSlice, columnDataTypeSpecification)
}
return columnDataTypeSpecificationsSlice
}
// type OutboundContactListInstance struct{
// }
// func (*OutboundContactListInstance) ResourceOutboundContactList() *schema.Resource {
// ResourceOutboundContactList() *schema.Resource
// }
func GeneratePhoneColumnsBlock(columnName, columnType, callableTimeColumn string) string {
return fmt.Sprintf(`
phone_columns {
column_name = "%s"
type = "%s"
callable_time_column = %s
}
`, columnName, columnType, callableTimeColumn)
}
func GenerateOutboundContactList(
resourceId string,
name string,
divisionId string,
previewModeColumnName string,
previewModeAcceptedValues []string,
columnNames []string,
automaticTimeZoneMapping string,
zipCodeColumnName string,
attemptLimitId string,
nestedBlocks ...string) string {
return fmt.Sprintf(`
resource "genesyscloud_outbound_contact_list" "%s" {
name = "%s"
division_id = %s
preview_mode_column_name = %s
preview_mode_accepted_values = [%s]
column_names = [%s]
automatic_time_zone_mapping = %s
zip_code_column_name = %s
attempt_limit_id = %s
%s
}
`, resourceId, name, divisionId, previewModeColumnName, strings.Join(previewModeAcceptedValues, ", "),
strings.Join(columnNames, ", "), automaticTimeZoneMapping, zipCodeColumnName, attemptLimitId, strings.Join(nestedBlocks, "\n"))
}
func GeneratePhoneColumnsDataTypeSpecBlock(columnName, columnDataType, min, max, maxLength string) string {
return fmt.Sprintf(`
column_data_type_specifications {
column_name = %s
column_data_type = %s
min = %s
max = %s
max_length = %s
}
`, columnName, columnDataType, min, max, maxLength)
}
package outbound_contactlistfilter
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
)
/*
The data_source_genesyscloud_outbound_contactlistfilter.go contains the data source implementation
for the resource.
*/
// dataSourceOutboundContactlistfilterRead retrieves by name the id in question
func dataSourceOutboundContactlistfilterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundContactlistfilterProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
contactListFilterId, retryable, resp, err := proxy.getOutboundContactlistfilterIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting contact list filter %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no contact list filters found with name %s", name), resp))
}
d.SetId(contactListFilterId)
return nil
})
}
package outbound_contactlistfilter
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
)
/*
The genesyscloud_outbound_contactlistfilter_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundContactlistfilterProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOutboundContactlistfilterFunc func(ctx context.Context, p *outboundContactlistfilterProxy, contactListFilter *platformclientv2.Contactlistfilter) (*platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error)
type getAllOutboundContactlistfilterFunc func(ctx context.Context, p *outboundContactlistfilterProxy, name string) (*[]platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error)
type getOutboundContactlistfilterIdByNameFunc func(ctx context.Context, p *outboundContactlistfilterProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getOutboundContactlistfilterByIdFunc func(ctx context.Context, p *outboundContactlistfilterProxy, id string) (contactListFilter *platformclientv2.Contactlistfilter, response *platformclientv2.APIResponse, err error)
type updateOutboundContactlistfilterFunc func(ctx context.Context, p *outboundContactlistfilterProxy, id string, contactListFilter *platformclientv2.Contactlistfilter) (*platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error)
type deleteOutboundContactlistfilterFunc func(ctx context.Context, p *outboundContactlistfilterProxy, id string) (response *platformclientv2.APIResponse, err error)
// outboundContactlistfilterProxy contains all of the methods that call genesys cloud APIs.
type outboundContactlistfilterProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundContactlistfilterAttr createOutboundContactlistfilterFunc
getAllOutboundContactlistfilterAttr getAllOutboundContactlistfilterFunc
getOutboundContactlistfilterIdByNameAttr getOutboundContactlistfilterIdByNameFunc
getOutboundContactlistfilterByIdAttr getOutboundContactlistfilterByIdFunc
updateOutboundContactlistfilterAttr updateOutboundContactlistfilterFunc
deleteOutboundContactlistfilterAttr deleteOutboundContactlistfilterFunc
}
// newOutboundContactlistfilterProxy initializes the outbound contactlistfilter proxy with all of the data needed to communicate with Genesys Cloud
func newOutboundContactlistfilterProxy(clientConfig *platformclientv2.Configuration) *outboundContactlistfilterProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundContactlistfilterProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundContactlistfilterAttr: createOutboundContactlistfilterFn,
getAllOutboundContactlistfilterAttr: getAllOutboundContactlistfilterFn,
getOutboundContactlistfilterIdByNameAttr: getOutboundContactlistfilterIdByNameFn,
getOutboundContactlistfilterByIdAttr: getOutboundContactlistfilterByIdFn,
updateOutboundContactlistfilterAttr: updateOutboundContactlistfilterFn,
deleteOutboundContactlistfilterAttr: deleteOutboundContactlistfilterFn,
}
}
// getOutboundContactlistfilterProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundContactlistfilterProxy(clientConfig *platformclientv2.Configuration) *outboundContactlistfilterProxy {
if internalProxy == nil {
internalProxy = newOutboundContactlistfilterProxy(clientConfig)
}
return internalProxy
}
// createOutboundContactlistfilter creates a Genesys Cloud outbound contactlistfilter
func (p *outboundContactlistfilterProxy) createOutboundContactlistfilter(ctx context.Context, outboundContactlistfilter *platformclientv2.Contactlistfilter) (*platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error) {
return p.createOutboundContactlistfilterAttr(ctx, p, outboundContactlistfilter)
}
// getOutboundContactlistfilter retrieves all Genesys Cloud outbound contactlistfilter
func (p *outboundContactlistfilterProxy) getAllOutboundContactlistfilter(ctx context.Context) (*[]platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error) {
return p.getAllOutboundContactlistfilterAttr(ctx, p, "")
}
// getOutboundContactlistfilterIdByName returns a single Genesys Cloud outbound contactlistfilter by a name
func (p *outboundContactlistfilterProxy) getOutboundContactlistfilterIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundContactlistfilterIdByNameAttr(ctx, p, name)
}
// getOutboundContactlistfilterById returns a single Genesys Cloud outbound contactlistfilter by Id
func (p *outboundContactlistfilterProxy) getOutboundContactlistfilterById(ctx context.Context, id string) (outboundContactlistfilter *platformclientv2.Contactlistfilter, response *platformclientv2.APIResponse, err error) {
return p.getOutboundContactlistfilterByIdAttr(ctx, p, id)
}
// updateOutboundContactlistfilter updates a Genesys Cloud outbound contactlistfilter
func (p *outboundContactlistfilterProxy) updateOutboundContactlistfilter(ctx context.Context, id string, outboundContactlistfilter *platformclientv2.Contactlistfilter) (*platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error) {
return p.updateOutboundContactlistfilterAttr(ctx, p, id, outboundContactlistfilter)
}
// deleteOutboundContactlistfilter deletes a Genesys Cloud outbound contactlistfilter by Id
func (p *outboundContactlistfilterProxy) deleteOutboundContactlistfilter(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundContactlistfilterAttr(ctx, p, id)
}
// createOutboundContactlistfilterFn is an implementation function for creating a Genesys Cloud outbound contactlistfilter
func createOutboundContactlistfilterFn(ctx context.Context, p *outboundContactlistfilterProxy, outboundContactlistfilter *platformclientv2.Contactlistfilter) (*platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error) {
contactListFilter, resp, err := p.outboundApi.PostOutboundContactlistfilters(*outboundContactlistfilter)
if err != nil {
return nil, resp, err
}
return contactListFilter, resp, nil
}
// getAllOutboundContactlistfilterFn is the implementation for retrieving all outbound contactlistfilter in Genesys Cloud
func getAllOutboundContactlistfilterFn(ctx context.Context, p *outboundContactlistfilterProxy, name string) (*[]platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error) {
var allContactlistfilters []platformclientv2.Contactlistfilter
const pageSize = 100
contactListFilters, resp, err := p.outboundApi.GetOutboundContactlistfilters(pageSize, 1, true, "", name, "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("failed to get page of contact list filter: %v", err)
}
if contactListFilters.Entities == nil || len(*contactListFilters.Entities) == 0 {
return &allContactlistfilters, resp, nil
}
for _, contactListFilter := range *contactListFilters.Entities {
allContactlistfilters = append(allContactlistfilters, contactListFilter)
}
for pageNum := 2; pageNum <= *contactListFilters.PageCount; pageNum++ {
contactListFilters, resp, err := p.outboundApi.GetOutboundContactlistfilters(pageSize, pageNum, true, "", name, "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("failed to get page of contact list filter: %v", err)
}
if contactListFilters.Entities == nil || len(*contactListFilters.Entities) == 0 {
break
}
for _, contactListFilter := range *contactListFilters.Entities {
allContactlistfilters = append(allContactlistfilters, contactListFilter)
}
}
return &allContactlistfilters, resp, nil
}
// getOutboundContactlistfilterIdByNameFn is an implementation of the function to get a Genesys Cloud outbound contactlistfilter by name
func getOutboundContactlistfilterIdByNameFn(ctx context.Context, p *outboundContactlistfilterProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
contactListFilters, resp, err := getAllOutboundContactlistfilterFn(ctx, p, name)
if err != nil {
return "", false, resp, fmt.Errorf("error searching outbound contact list filter %s: %s", name, err)
}
var filter platformclientv2.Contactlistfilter
for _, contactListFilter := range *contactListFilters {
if *contactListFilter.Name == name {
log.Printf("Retrieved the contact list filter id %s by name %s", *contactListFilter.Id, name)
filter = contactListFilter
return *filter.Id, false, resp, nil
}
}
return "", true, resp, nil
}
// getOutboundContactlistfilterByIdFn is an implementation of the function to get a Genesys Cloud outbound contactlistfilter by Id
func getOutboundContactlistfilterByIdFn(ctx context.Context, p *outboundContactlistfilterProxy, id string) (outboundContactlistfilter *platformclientv2.Contactlistfilter, response *platformclientv2.APIResponse, err error) {
contactListFilter, resp, err := p.outboundApi.GetOutboundContactlistfilter(id)
if err != nil {
return nil, resp, err
}
return contactListFilter, resp, nil
}
// updateOutboundContactlistfilterFn is an implementation of the function to update a Genesys Cloud outbound contactlistfilter
func updateOutboundContactlistfilterFn(ctx context.Context, p *outboundContactlistfilterProxy, id string, outboundContactlistfilter *platformclientv2.Contactlistfilter) (*platformclientv2.Contactlistfilter, *platformclientv2.APIResponse, error) {
contactListFilter, resp, err := p.outboundApi.GetOutboundContactlistfilter(id)
if err != nil {
return nil, resp, err
}
outboundContactlistfilter.Version = contactListFilter.Version
outboundContactlistfilter, resp, updateErr := p.outboundApi.PutOutboundContactlistfilter(id, *outboundContactlistfilter)
if updateErr != nil {
return nil, resp, updateErr
}
return outboundContactlistfilter, resp, nil
}
// deleteOutboundContactlistfilterFn is an implementation function for deleting a Genesys Cloud outbound contactlistfilter
func deleteOutboundContactlistfilterFn(ctx context.Context, p *outboundContactlistfilterProxy, id string) (response *platformclientv2.APIResponse, err error) {
return p.outboundApi.DeleteOutboundContactlistfilter(id)
}
package outbound_contactlistfilter
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
)
/*
The resource_genesyscloud_outbound_contactlistfilter.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthOutboundContactlistfilter retrieves all of the outbound contactlistfilter via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthOutboundContactlistfilters(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getOutboundContactlistfilterProxy(clientConfig)
contactListFilters, resp, err := proxy.getAllOutboundContactlistfilter(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get contact list filters error: %s", err), resp)
}
for _, contactListFilter := range *contactListFilters {
resources[*contactListFilter.Id] = &resourceExporter.ResourceMeta{Name: *contactListFilter.Name}
}
return resources, nil
}
// createOutboundContactlistfilter is used by the outbound_contactlistfilter resource to create Genesys cloud outbound contactlistfilter
func createOutboundContactlistfilter(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundContactlistfilterProxy(sdkConfig)
contactListFilter := getContactlistfilterFromResourceData(d)
log.Printf("Creating Outbound Contact List Filter %s", *contactListFilter.Name)
outboundContactListFilter, resp, err := proxy.createOutboundContactlistfilter(ctx, &contactListFilter)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Contact List Filter %s error: %s", *contactListFilter.Name, err), resp)
}
d.SetId(*outboundContactListFilter.Id)
log.Printf("Created Outbound Contact List Filter %s %s", *contactListFilter.Name, *outboundContactListFilter.Id)
return readOutboundContactlistfilter(ctx, d, meta)
}
// readOutboundContactlistfilter is used by the outbound_contactlistfilter resource to read an outbound contactlistfilter from genesys cloud
func readOutboundContactlistfilter(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundContactlistfilterProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundContactlistfilter(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Contact List Filter %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkContactListFilter, resp, getErr := proxy.getOutboundContactlistfilterById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List Filter %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List Filter %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", sdkContactListFilter.Name)
resourcedata.SetNillableReference(d, "contact_list_id", sdkContactListFilter.ContactList)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "clauses", sdkContactListFilter.Clauses, flattenContactListFilterClauses)
resourcedata.SetNillableValue(d, "filter_type", sdkContactListFilter.FilterType)
log.Printf("Read Outbound Contact List Filter %s %s", d.Id(), *sdkContactListFilter.Name)
return cc.CheckState(d)
})
}
// updateOutboundContactlistfilter is used by the outbound_contactlistfilter resource to update an outbound contactlistfilter in Genesys Cloud
func updateOutboundContactlistfilter(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundContactlistfilterProxy(sdkConfig)
contactListFilter := getContactlistfilterFromResourceData(d)
log.Printf("Updating Outbound Contact List Filter %s", *contactListFilter.Name)
_, resp, err := proxy.updateOutboundContactlistfilter(ctx, d.Id(), &contactListFilter)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound Contact List Filter %s error: %s", *contactListFilter.Name, err), resp)
}
log.Printf("Updated Outbound Contact List Filter %s", *contactListFilter.Name)
return readOutboundContactlistfilter(ctx, d, meta)
}
// deleteOutboundContactlistfilter is used by the outbound_contactlistfilter resource to delete an outbound contactlistfilter from Genesys cloud
func deleteOutboundContactlistfilter(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundContactlistfilterProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound Contact List Filter")
resp, err := proxy.deleteOutboundContactlistfilter(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound Contact List Filter %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getOutboundContactlistfilterById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound Contact list filter deleted
log.Printf("Deleted Outbound Contact List Filter %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Contact List Filter %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Contact List Filter %s still exists", d.Id()), resp))
})
}
package outbound_contactlistfilter
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_outbound_contactlistfilter_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the outbound_contactlistfilter resource.
3. The datasource schema definitions for the outbound_contactlistfilter datasource.
4. The resource exporter configuration for the outbound_contactlistfilter exporter.
*/
const resourceName = "genesyscloud_outbound_contactlistfilter"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceOutboundContactlistfilter())
regInstance.RegisterDataSource(resourceName, DataSourceOutboundContactlistfilter())
regInstance.RegisterExporter(resourceName, OutboundContactlistfilterExporter())
}
var (
filterClauseResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`filter_type`: {
Description: `How to join predicates together.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`AND`, `OR`}, false),
},
`predicates`: {
Description: `Conditions to filter the contacts by.`,
Optional: true,
Type: schema.TypeList,
Elem: predicateResource,
},
},
}
predicateResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`column`: {
Description: `Contact list column from the contact list filter's contact list.`,
Optional: true,
Type: schema.TypeString,
},
`column_type`: {
Description: `The type of data in the contact column.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`numeric`, `alphabetic`}, false),
},
`operator`: {
Description: `The operator for this contact list filter predicate.`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`EQUALS`, `LESS_THAN`, `LESS_THAN_EQUALS`, `GREATER_THAN`, `GREATER_THAN_EQUALS`, `CONTAINS`, `BEGINS_WITH`, `ENDS_WITH`, `BEFORE`, `AFTER`, `BETWEEN`, `IN`}, false),
},
`value`: {
Description: `Value with which to compare the contact's data. This could be text, a number, or a relative time. A value for relative time should follow the format PxxDTyyHzzM, where xx, yy, and zz specify the days, hours and minutes. For example, a value of P01DT08H30M corresponds to 1 day, 8 hours, and 30 minutes from now. To specify a time in the past, include a negative sign before each numeric value. For example, a value of P-01DT-08H-30M corresponds to 1 day, 8 hours, and 30 minutes in the past. You can also do things like P01DT00H-30M, which would correspond to 23 hours and 30 minutes from now (1 day - 30 minutes).`,
Required: true,
Type: schema.TypeString,
},
`var_range`: {
Description: `A range of values. Required for operators BETWEEN and IN.`,
Optional: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: rangeResource,
},
`inverted`: {
Description: `Inverts the result of the predicate (i.e., if the predicate returns true, inverting it will return false).`,
Optional: true,
Computed: true,
Type: schema.TypeBool,
},
},
}
rangeResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`min`: {
Description: `The minimum value of the range. Required for the operator BETWEEN.`,
Optional: true,
Type: schema.TypeString,
},
`max`: {
Description: `The maximum value of the range. Required for the operator BETWEEN.`,
Optional: true,
Type: schema.TypeString,
},
`min_inclusive`: {
Description: `Whether or not to include the minimum in the range.`,
Optional: true,
Type: schema.TypeBool,
},
`max_inclusive`: {
Description: `Whether or not to include the maximum in the range.`,
Optional: true,
Type: schema.TypeBool,
},
`in_set`: {
Description: `A set of values that the contact data should be in. Required for the IN operator.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
// ResourceOutboundContactlistfilter registers the genesyscloud_outbound_contactlistfilter resource with Terraform
func ResourceOutboundContactlistfilter() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Outbound Contact List Filter`,
CreateContext: provider.CreateWithPooledClient(createOutboundContactlistfilter),
ReadContext: provider.ReadWithPooledClient(readOutboundContactlistfilter),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundContactlistfilter),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundContactlistfilter),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the list.`,
Required: true,
Type: schema.TypeString,
},
`contact_list_id`: {
Description: `The contact list the filter is based on.`,
Required: true,
Type: schema.TypeString,
},
`clauses`: {
Description: `Groups of conditions to filter the contacts by.`,
Optional: true,
Type: schema.TypeList,
Elem: filterClauseResource,
},
`filter_type`: {
Description: `How to join clauses together.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`AND`, `OR`}, false),
},
},
}
}
// OutboundContactlistfilterExporter returns the resourceExporter object used to hold the genesyscloud_outbound_contactlistfilter exporter's config
func OutboundContactlistfilterExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthOutboundContactlistfilters),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"contact_list_id": {RefType: "genesyscloud_outbound_contact_list"},
},
}
}
// DataSourceOutboundContactlistfilter registers the genesyscloud_outbound_contactlistfilter data source
func DataSourceOutboundContactlistfilter() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Outbound Contact List Filters. Select a contact list filter by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundContactlistfilterRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: "Contact List Filter name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package outbound_contactlistfilter
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
func getContactlistfilterFromResourceData(d *schema.ResourceData) platformclientv2.Contactlistfilter {
filter := platformclientv2.Contactlistfilter{
Name: platformclientv2.String(d.Get("name").(string)),
ContactList: util.BuildSdkDomainEntityRef(d, "contact_list_id"),
Clauses: buildContactListFilterClauses(d.Get("clauses").([]interface{})),
}
filterType := d.Get("filter_type").(string)
if filterType != "" {
filter.FilterType = &filterType
}
return filter
}
func buildContactListFilterClauses(clauses []interface{}) *[]platformclientv2.Contactlistfilterclause {
if clauses == nil || len(clauses) == 0 {
return nil
}
sdkClauses := make([]platformclientv2.Contactlistfilterclause, 0)
for _, clause := range clauses {
var sdkClause platformclientv2.Contactlistfilterclause
contactListFilterMap := clause.(map[string]interface{})
resourcedata.BuildSDKStringValueIfNotNil(&sdkClause.FilterType, contactListFilterMap, "filter_type")
if predicates := contactListFilterMap["predicates"]; predicates != nil {
sdkClause.Predicates = buildContactListFilterPredicate(predicates.([]interface{}))
}
sdkClauses = append(sdkClauses, sdkClause)
}
return &sdkClauses
}
func buildContactListFilterPredicate(predicates []interface{}) *[]platformclientv2.Contactlistfilterpredicate {
if predicates == nil || len(predicates) == 0 {
return nil
}
sdkPredicates := make([]platformclientv2.Contactlistfilterpredicate, 0)
for _, predicate := range predicates {
if predicateMap, ok := predicate.(map[string]interface{}); ok {
var sdkPredicate platformclientv2.Contactlistfilterpredicate
resourcedata.BuildSDKStringValueIfNotNil(&sdkPredicate.Column, predicateMap, "column")
resourcedata.BuildSDKStringValueIfNotNil(&sdkPredicate.ColumnType, predicateMap, "column_type")
resourcedata.BuildSDKStringValueIfNotNil(&sdkPredicate.Operator, predicateMap, "operator")
resourcedata.BuildSDKStringValueIfNotNil(&sdkPredicate.Value, predicateMap, "value")
if varRangeSet := predicateMap["var_range"].(*schema.Set); varRangeSet != nil && len(varRangeSet.List()) > 0 {
sdkPredicate.VarRange = buildContactListFilterRange(varRangeSet)
}
if inverted, ok := predicateMap["inverted"].(bool); ok {
sdkPredicate.Inverted = &inverted
}
sdkPredicates = append(sdkPredicates, sdkPredicate)
}
}
return &sdkPredicates
}
func buildContactListFilterRange(contactListFilterRange *schema.Set) *platformclientv2.Contactlistfilterrange {
contactListFilterRangeList := contactListFilterRange.List()
contactListFilterRangeMap := contactListFilterRangeList[0].(map[string]interface{})
var sdkContactListFilterRange platformclientv2.Contactlistfilterrange
resourcedata.BuildSDKStringValueIfNotNil(&sdkContactListFilterRange.Min, contactListFilterRangeMap, "min")
resourcedata.BuildSDKStringValueIfNotNil(&sdkContactListFilterRange.Max, contactListFilterRangeMap, "max")
if minInclusive, ok := contactListFilterRangeMap["min_inclusive"].(bool); ok {
sdkContactListFilterRange.MinInclusive = &minInclusive
}
if maxInclusive, ok := contactListFilterRangeMap["max_inclusive"].(bool); ok {
sdkContactListFilterRange.MaxInclusive = &maxInclusive
}
inSet := make([]string, 0)
for _, v := range contactListFilterRangeMap["in_set"].([]interface{}) {
inSet = append(inSet, v.(string))
}
sdkContactListFilterRange.InSet = &inSet
return &sdkContactListFilterRange
}
func flattenContactListFilterClauses(contactListFilterClauses *[]platformclientv2.Contactlistfilterclause) []interface{} {
if len(*contactListFilterClauses) == 0 {
return nil
}
contactListFilterClauseList := make([]interface{}, 0)
for _, contactListFilterClause := range *contactListFilterClauses {
contactListFilterClauseMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(contactListFilterClauseMap, "filter_type", contactListFilterClause.FilterType)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(contactListFilterClauseMap, "predicates", contactListFilterClause.Predicates, flattenContactListFilterPredicates)
contactListFilterClauseList = append(contactListFilterClauseList, contactListFilterClauseMap)
}
return contactListFilterClauseList
}
func flattenContactListFilterPredicates(contactListFilterPredicates *[]platformclientv2.Contactlistfilterpredicate) []interface{} {
if len(*contactListFilterPredicates) == 0 {
return nil
}
contactListFilterPredicateList := make([]interface{}, 0)
for _, contactListFilterPredicate := range *contactListFilterPredicates {
contactListFilterPredicateMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(contactListFilterPredicateMap, "column", contactListFilterPredicate.Column)
resourcedata.SetMapValueIfNotNil(contactListFilterPredicateMap, "column_type", contactListFilterPredicate.ColumnType)
resourcedata.SetMapValueIfNotNil(contactListFilterPredicateMap, "operator", contactListFilterPredicate.Operator)
resourcedata.SetMapValueIfNotNil(contactListFilterPredicateMap, "value", contactListFilterPredicate.Value)
resourcedata.SetMapValueIfNotNil(contactListFilterPredicateMap, "inverted", contactListFilterPredicate.Inverted)
resourcedata.SetMapSchemaSetWithFuncIfNotNil(contactListFilterPredicateMap, "var_range", contactListFilterPredicate.VarRange, flattenContactListFilterRange)
contactListFilterPredicateList = append(contactListFilterPredicateList, contactListFilterPredicateMap)
}
return contactListFilterPredicateList
}
func flattenContactListFilterRange(contactListFilterRange *platformclientv2.Contactlistfilterrange) *schema.Set {
if contactListFilterRange == nil {
return nil
}
contactListFilterRangeSet := schema.NewSet(schema.HashResource(rangeResource), []interface{}{})
contactListFilterRangeMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(contactListFilterRangeMap, "min", contactListFilterRange.Min)
resourcedata.SetMapValueIfNotNil(contactListFilterRangeMap, "max", contactListFilterRange.Max)
resourcedata.SetMapValueIfNotNil(contactListFilterRangeMap, "min_inclusive", contactListFilterRange.MinInclusive)
resourcedata.SetMapValueIfNotNil(contactListFilterRangeMap, "max_inclusive", contactListFilterRange.MaxInclusive)
if contactListFilterRange.InSet != nil {
// Changed []string to []interface{} to prevent type conversion panic
inSet := make([]interface{}, 0)
for _, v := range *contactListFilterRange.InSet {
inSet = append(inSet, v)
}
contactListFilterRangeMap["in_set"] = inSet
}
if len(contactListFilterRangeMap) == 0 {
return nil
}
contactListFilterRangeSet.Add(contactListFilterRangeMap)
return contactListFilterRangeSet
}
func GenerateOutboundContactListFilter(
resourceId string,
name string,
contactListId string,
filterType string,
nestedBlocks ...string,
) string {
if filterType != "" {
filterType = fmt.Sprintf(`filter_type = "%s"`, filterType)
}
return fmt.Sprintf(`
resource "genesyscloud_outbound_contactlistfilter" "%s" {
name = "%s"
contact_list_id = %s
%s
%s
}
`, resourceId, name, contactListId, filterType, strings.Join(nestedBlocks, "\n"))
}
func GenerateOutboundContactListFilterClause(filterType string, nestedBlocks ...string) string {
if filterType != "" {
filterType = fmt.Sprintf(`filter_type = "%s"`, filterType)
}
return fmt.Sprintf(`
clauses {
%s
%s
}
`, filterType, strings.Join(nestedBlocks, "\n"))
}
func GenerateOutboundContactListFilterPredicates(
column string,
columnType string,
operator string,
value string,
inverted string,
varRangeBlock string,
) string {
if column != "" {
column = fmt.Sprintf(`column = "%s"`, column)
}
if columnType != "" {
columnType = fmt.Sprintf(`column_type = "%s"`, columnType)
}
if operator != "" {
operator = fmt.Sprintf(`operator = "%s"`, operator)
}
if value != "" {
value = fmt.Sprintf(`value = "%s"`, value)
}
if inverted != "" {
inverted = fmt.Sprintf(`inverted = %s`, inverted)
}
return fmt.Sprintf(`
predicates {
%s
%s
%s
%s
%s
%s
}
`, column, columnType, operator, value, inverted, varRangeBlock)
}
package outbound_dnclist
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceOutboundDncListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundDnclistProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
dnclistId, retryable, resp, getErr := proxy.getOutboundDnclistByName(ctx, name)
if getErr != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting dnc lists %s | error: %s", name, getErr), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no dnc lists found with name %s", name), resp))
}
d.SetId(dnclistId)
return nil
})
}
package outbound_dnclist
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/util"
)
var internalProxy *outboundDnclistProxy
// type definitions for each func on our proxy
type createOutboundDnclistFunc func(ctx context.Context, p *outboundDnclistProxy, dnclist *platformclientv2.Dnclistcreate) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error)
type getAllOutboundDnclistFunc func(ctx context.Context, p *outboundDnclistProxy) (*[]platformclientv2.Dnclist, *platformclientv2.APIResponse, error)
type getOutboundDnclistByIdFunc func(ctx context.Context, p *outboundDnclistProxy, dnclistId string) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error)
type getOutboundDnclistByNameFunc func(ctx context.Context, p *outboundDnclistProxy, name string) (dnclistId string, retryable bool, response *platformclientv2.APIResponse, err error)
type updateOutboundDnclistFunc func(ctx context.Context, p *outboundDnclistProxy, dnclistId string, dnclist *platformclientv2.Dnclist) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error)
type deleteOutboundDnclistFunc func(ctx context.Context, p *outboundDnclistProxy, dnclistId string) (*platformclientv2.APIResponse, error)
type uploadPhoneEntriesToDncListFunc func(p *outboundDnclistProxy, dncList *platformclientv2.Dnclist, entry interface{}) (*platformclientv2.APIResponse, diag.Diagnostics)
// outboundDnclistProxy contains all the methods that call genesys cloud APIs
type outboundDnclistProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundDnclistAttr createOutboundDnclistFunc
getAllOutboundDnclistAttr getAllOutboundDnclistFunc
getOutboundDnclistByIdAttr getOutboundDnclistByIdFunc
getOutboundDnclistByNameAttr getOutboundDnclistByNameFunc
updateOutboundDnclistAttr updateOutboundDnclistFunc
deleteOutboundDnclistAttr deleteOutboundDnclistFunc
uploadPhoneEntriesToDncListAttr uploadPhoneEntriesToDncListFunc
}
// newOutboundDnclistProxy initializes the dnclist proxy with the data needed for communication with the genesys cloud
func newOutboundDnclistProxy(clientConfig *platformclientv2.Configuration) *outboundDnclistProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundDnclistProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundDnclistAttr: createOutboundDnclistFn,
getAllOutboundDnclistAttr: getAllOutboundDnclistFn,
getOutboundDnclistByIdAttr: getOutboundDnclistByIdFn,
getOutboundDnclistByNameAttr: getOutboundDnclistByNameFn,
updateOutboundDnclistAttr: updateOutboundDnclistFn,
deleteOutboundDnclistAttr: deleteOutboundDnclistFn,
uploadPhoneEntriesToDncListAttr: uploadPhoneEntriesToDncListFn,
}
}
func getOutboundDnclistProxy(clientConfig *platformclientv2.Configuration) *outboundDnclistProxy {
if internalProxy == nil {
internalProxy = newOutboundDnclistProxy(clientConfig)
}
return internalProxy
}
// createOutboundDnclist creates a Genesys Cloud Outbound Dnclist
func (p *outboundDnclistProxy) createOutboundDnclist(ctx context.Context, dnclist *platformclientv2.Dnclistcreate) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
return p.createOutboundDnclistAttr(ctx, p, dnclist)
}
// getAllOutboundDnclist retrieves all Genesys Cloud Outbound Dnclists
func (p *outboundDnclistProxy) getAllOutboundDnclist(ctx context.Context) (*[]platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
return p.getAllOutboundDnclistAttr(ctx, p)
}
// getOutboundDnclistById returns a single Genesys Cloud Outbound Dnclist by Id
func (p *outboundDnclistProxy) getOutboundDnclistById(ctx context.Context, dnclistId string) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
return p.getOutboundDnclistByIdAttr(ctx, p, dnclistId)
}
// getOutboundDnclistByName returns a single Genesys Cloud Outbound Dnclist by a name
func (p *outboundDnclistProxy) getOutboundDnclistByName(ctx context.Context, name string) (dnclistId string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundDnclistByNameAttr(ctx, p, name)
}
// updateOutboundDnclist updates a Genesys Cloud Outbound Dnclist
func (p *outboundDnclistProxy) updateOutboundDnclist(ctx context.Context, dnclistId string, dnclist *platformclientv2.Dnclist) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
return p.updateOutboundDnclistAttr(ctx, p, dnclistId, dnclist)
}
// deleteOutboundDnclist deletes a Genesys Cloud Outbound Dnclist by Id
func (p *outboundDnclistProxy) deleteOutboundDnclist(ctx context.Context, dnclistId string) (*platformclientv2.APIResponse, error) {
return p.deleteOutboundDnclistAttr(ctx, p, dnclistId)
}
func (p *outboundDnclistProxy) uploadPhoneEntriesToDncList(dncList *platformclientv2.Dnclist, entry interface{}) (*platformclientv2.APIResponse, diag.Diagnostics) {
return p.uploadPhoneEntriesToDncListAttr(p, dncList, entry)
}
func createOutboundDnclistFn(ctx context.Context, p *outboundDnclistProxy, dnclist *platformclientv2.Dnclistcreate) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
list, resp, err := p.outboundApi.PostOutboundDnclists(*dnclist)
if err != nil {
return nil, resp, fmt.Errorf("failed to create dnclist: %s", err)
}
return list, resp, nil
}
func getAllOutboundDnclistFn(ctx context.Context, p *outboundDnclistProxy) (*[]platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
var allDnclists []platformclientv2.Dnclist
const pageSize = 100
dnclists, resp, err := p.outboundApi.GetOutboundDnclists(false, false, pageSize, 1, true, "", "", "", []string{}, "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get dnclists: %v", err)
}
if dnclists.Entities == nil || len(*dnclists.Entities) == 0 {
return &allDnclists, resp, nil
}
for _, dnclist := range *dnclists.Entities {
allDnclists = append(allDnclists, dnclist)
}
var response *platformclientv2.APIResponse
for pageNum := 2; pageNum <= *dnclists.PageCount; pageNum++ {
dnclists, resp, err := p.outboundApi.GetOutboundDnclists(false, false, pageSize, pageNum, true, "", "", "", []string{}, "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get dnclists: %v", err)
}
response = resp
if dnclists.Entities == nil || len(*dnclists.Entities) == 0 {
break
}
for _, dnclist := range *dnclists.Entities {
log.Printf("Dealing with dnclist: %s", *dnclist.Id)
allDnclists = append(allDnclists, dnclist)
}
}
return &allDnclists, response, nil
}
func updateOutboundDnclistFn(ctx context.Context, p *outboundDnclistProxy, dnclistId string, dnclist *platformclientv2.Dnclist) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
dnclist, resp, err := p.outboundApi.GetOutboundDnclist(dnclistId, false, false)
if err != nil {
return nil, resp, fmt.Errorf("failed to get dnc list by id %s", err)
}
outboundDncList, resp, err := p.outboundApi.PutOutboundDnclist(dnclistId, *dnclist)
if err != nil {
return nil, resp, fmt.Errorf("error updating outbound dnc list %s", err)
}
return outboundDncList, resp, nil
}
func uploadPhoneEntriesToDncListFn(p *outboundDnclistProxy, dncList *platformclientv2.Dnclist, entry interface{}) (*platformclientv2.APIResponse, diag.Diagnostics) {
var phoneNumbers []string
var resp *platformclientv2.APIResponse
if entryMap, ok := entry.(map[string]interface{}); ok && len(entryMap) > 0 {
if phoneNumbersList := entryMap["phone_numbers"].([]interface{}); phoneNumbersList != nil {
for _, number := range phoneNumbersList {
phoneNumbers = append(phoneNumbers, number.(string))
}
}
log.Printf("Uploading phone numbers to DNC list %s", *dncList.Name)
// POST /api/v2/outbound/dnclists/{dncListId}/phonenumbers
response, err := p.outboundApi.PostOutboundDnclistPhonenumbers(*dncList.Id, phoneNumbers, entryMap["expiration_date"].(string))
if err != nil {
return response, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to upload phone numbers to Outbound DNC list %s: %s", *dncList.Name, err), response)
}
resp = response
log.Printf("Uploaded phone numbers to DNC list %s", *dncList.Name)
}
return resp, nil
}
func deleteOutboundDnclistFn(ctx context.Context, p *outboundDnclistProxy, dnclistId string) (*platformclientv2.APIResponse, error) {
resp, err := p.outboundApi.DeleteOutboundDnclist(dnclistId)
if err != nil {
return resp, fmt.Errorf("failed to delete dnc list %s", err)
}
return resp, nil
}
func getOutboundDnclistByIdFn(ctx context.Context, p *outboundDnclistProxy, dnclistId string) (*platformclientv2.Dnclist, *platformclientv2.APIResponse, error) {
dnclist, resp, err := p.outboundApi.GetOutboundDnclist(dnclistId, false, false)
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve dnc list by id %s: %s", dnclistId, err)
}
return dnclist, resp, nil
}
func getOutboundDnclistByNameFn(ctx context.Context, p *outboundDnclistProxy, name string) (dnclistId string, retryable bool, response *platformclientv2.APIResponse, err error) {
dnclists, resp, err := getAllOutboundDnclistFn(ctx, p)
if err != nil {
return "", false, resp, fmt.Errorf("Error searching outbound dnc list %s: %s", name, err)
}
var dnclist platformclientv2.Dnclist
for _, dnclistSdk := range *dnclists {
if *dnclistSdk.Name == name {
log.Printf("Retrieved the dnc list id %s by name %s", *dnclistSdk.Id, name)
dnclist = dnclistSdk
return *dnclist.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find dnc list with name %s", name)
}
package outbound_dnclist
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllOutboundDncLists(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getOutboundDnclistProxy(clientConfig)
dnclists, resp, err := proxy.getAllOutboundDnclist(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get dnclists error: %s", err), resp)
}
for _, dncListConfig := range *dnclists {
resources[*dncListConfig.Id] = &resourceExporter.ResourceMeta{Name: *dncListConfig.Name}
}
return resources, nil
}
func createOutboundDncList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
contactMethod := d.Get("contact_method").(string)
loginId := d.Get("login_id").(string)
campaignId := d.Get("campaign_id").(string)
licenseId := d.Get("license_id").(string)
dncSourceType := d.Get("dnc_source_type").(string)
dncCodes := lists.InterfaceListToStrings(d.Get("dnc_codes").([]interface{}))
entries := d.Get("entries").([]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundDnclistProxy(sdkConfig)
sdkDncListCreate := platformclientv2.Dnclistcreate{
DncCodes: &dncCodes,
Division: util.BuildSdkDomainEntityRef(d, "division_id"),
}
if name != "" {
sdkDncListCreate.Name = &name
}
if contactMethod != "" {
sdkDncListCreate.ContactMethod = &contactMethod
}
if loginId != "" {
sdkDncListCreate.LoginId = &loginId
}
if campaignId != "" {
sdkDncListCreate.CampaignId = &campaignId
}
if licenseId != "" {
sdkDncListCreate.LicenseId = &licenseId
}
if dncSourceType != "" {
sdkDncListCreate.DncSourceType = &dncSourceType
}
log.Printf("Creating Outbound DNC list %s", name)
outboundDncList, resp, err := proxy.createOutboundDnclist(ctx, &sdkDncListCreate)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound DNC list %s error: %s", name, err), resp)
}
d.SetId(*outboundDncList.Id)
if len(entries) > 0 {
if *sdkDncListCreate.DncSourceType == "rds" {
for _, entry := range entries {
resp, err := proxy.uploadPhoneEntriesToDncList(outboundDncList, entry)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound DNC list %s error: %v", name, err), resp)
}
}
} else {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Phone numbers can only be uploaded to internal DNC lists."), fmt.Errorf("phone numbers can only be uploaded to internal DNC Lists"))
}
}
log.Printf("Created Outbound DNC list %s %s", name, *outboundDncList.Id)
return readOutboundDncList(ctx, d, meta)
}
func updateOutboundDncList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
contactMethod := d.Get("contact_method").(string)
loginId := d.Get("login_id").(string)
campaignId := d.Get("campaign_id").(string)
dncCodes := lists.InterfaceListToStrings(d.Get("dnc_codes").([]interface{}))
licenseId := d.Get("license_id").(string)
dncSourceType := d.Get("dnc_source_type").(string)
entries := d.Get("entries").([]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundDnclistProxy(sdkConfig)
sdkDncList := platformclientv2.Dnclist{
DncCodes: &dncCodes,
Division: util.BuildSdkDomainEntityRef(d, "division_id"),
}
if name != "" {
sdkDncList.Name = &name
}
if contactMethod != "" {
sdkDncList.ContactMethod = &contactMethod
}
if loginId != "" {
sdkDncList.LoginId = &loginId
}
if campaignId != "" {
sdkDncList.CampaignId = &campaignId
}
if licenseId != "" {
sdkDncList.LicenseId = &licenseId
}
if dncSourceType != "" {
sdkDncList.DncSourceType = &dncSourceType
}
log.Printf("Updating Outbound DNC list %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current Outbound DNC list version
outboundDncList, resp, getErr := proxy.getOutboundDnclistById(ctx, d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound DNC list %s error: %s", name, getErr), resp)
}
sdkDncList.Version = outboundDncList.Version
outboundDncList, response, updateErr := proxy.updateOutboundDnclist(ctx, d.Id(), &sdkDncList)
if updateErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound DNC list %s error: %s", name, updateErr), response)
}
if len(entries) > 0 {
if *sdkDncList.DncSourceType == "rds" {
for _, entry := range entries {
response, err := proxy.uploadPhoneEntriesToDncList(outboundDncList, entry)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound DNC list %s error: %v", name, err), response)
}
}
} else {
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Phone numbers can only be uploaded to internal DNC lists"), fmt.Errorf("phone numbers can only be uploaded to internal DNC lists"))
}
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Outbound DNC list %s", name)
return readOutboundDncList(ctx, d, meta)
}
func readOutboundDncList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundDnclistProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundDncList(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound DNC list %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkDncList, resp, getErr := proxy.getOutboundDnclistById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound DNC list %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound DNC list %s | error: %s", d.Id(), getErr), resp))
}
if sdkDncList.Name != nil {
_ = d.Set("name", *sdkDncList.Name)
}
if sdkDncList.ContactMethod != nil {
_ = d.Set("contact_method", *sdkDncList.ContactMethod)
}
if sdkDncList.LoginId != nil {
_ = d.Set("login_id", *sdkDncList.LoginId)
}
if sdkDncList.CampaignId != nil {
_ = d.Set("campaign_id", *sdkDncList.CampaignId)
}
if sdkDncList.DncCodes != nil {
schemaCodes := lists.InterfaceListToStrings(d.Get("dnc_codes").([]interface{}))
// preserve ordering and avoid a plan not empty error
if lists.AreEquivalent(schemaCodes, *sdkDncList.DncCodes) {
_ = d.Set("dnc_codes", schemaCodes)
} else {
_ = d.Set("dnc_codes", lists.StringListToInterfaceList(*sdkDncList.DncCodes))
}
}
if sdkDncList.DncSourceType != nil {
_ = d.Set("dnc_source_type", *sdkDncList.DncSourceType)
}
if sdkDncList.LicenseId != nil {
_ = d.Set("license_id", *sdkDncList.LicenseId)
}
if sdkDncList.Division != nil && sdkDncList.Division.Id != nil {
_ = d.Set("division_id", *sdkDncList.Division.Id)
}
log.Printf("Read Outbound DNC list %s %s", d.Id(), *sdkDncList.Name)
return cc.CheckState(d)
})
}
func deleteOutboundDncList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundDnclistProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound DNC list")
resp, err := proxy.deleteOutboundDnclist(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound DNC list %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getOutboundDnclistById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Outbound DNC list deleted
log.Printf("Deleted Outbound DNC list %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound DNC list %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound DNC list %s still exists", d.Id()), resp))
})
}
func GenerateOutboundDncListBasic(resourceId string, name string) string {
return fmt.Sprintf(`
resource "genesyscloud_outbound_dnclist" "%s" {
name = "%s"
dnc_source_type = "rds"
contact_method = "Phone"
}
`, resourceId, name)
}
package outbound_dnclist
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
const resourceName = "genesyscloud_outbound_dnclist"
// SetRegistrar registers all the resources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceOutboundDncList())
l.RegisterResource(resourceName, ResourceOutboundDncList())
l.RegisterExporter(resourceName, OutboundDncListExporter())
}
func ResourceOutboundDncList() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Outbound DNC List`,
CreateContext: provider.CreateWithPooledClient(createOutboundDncList),
ReadContext: provider.ReadWithPooledClient(readOutboundDncList),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundDncList),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundDncList),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the DncList.`,
Required: true,
Type: schema.TypeString,
},
`contact_method`: {
Description: `The contact method. Required if dncSourceType is rds.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`Email`, `Phone`}, false),
},
`login_id`: {
Description: `A dnc.com loginId. Required if the dncSourceType is dnc.com.`,
Optional: true,
Type: schema.TypeString,
},
`campaign_id`: {
Description: `A dnc.com campaignId. Optional if the dncSourceType is dnc.com.`,
Optional: true,
Type: schema.TypeString,
},
`dnc_codes`: {
Description: `The list of dnc.com codes to be treated as DNC. Required if the dncSourceType is dnc.com.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`B`, `C`, `D`, `E`, `F`, `G`, `H`, `I`, `L`, `M`, `O`, `P`, `R`, `S`, `T`, `V`, `W`, `X`, `Y`}, false),
},
},
`license_id`: {
Description: `A gryphon license number. Required if the dncSourceType is gryphon.`,
Optional: true,
Type: schema.TypeString,
},
`division_id`: {
Description: `The division this DNC List belongs to.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`dnc_source_type`: {
Description: `The type of the DNC List. Changing the dnc_source_attribute will cause the outbound_dnclist object to be dropped and recreated with new ID.`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`rds`, `dnc.com`, `gryphon`}, false),
},
`entries`: {
Description: `Rows to add to the DNC list. To emulate removing phone numbers, you can set expiration_date to a date in the past.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
`expiration_date`: {
Description: `Expiration date for DNC phone numbers in yyyy-MM-ddTHH:mmZ format.`,
Optional: true,
Type: schema.TypeString,
ValidateDiagFunc: gcloud.ValidateDateTime,
},
`phone_numbers`: {
Description: `Phone numbers to add to a DNC list. Only possible if the dncSourceType is rds. Phone numbers must be in an E.164 number format.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
},
},
},
},
},
}
}
func DataSourceOutboundDncList() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Outbound DNC Lists. Select a DNC list by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundDncListRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "DNC List name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func OutboundDncListExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundDncLists),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
},
}
}
package outbound_filespecificationtemplate
import (
"context"
"fmt"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceOutboundFileSpecificationTemplateRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundFilespecificationtemplateProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
fstId, retryable, resp, err := proxy.getOutboundFilespecificationtemplateIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting file specification template %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No file specification template found with name %s", name), resp))
}
d.SetId(fstId)
return nil
})
}
package outbound_filespecificationtemplate
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_outbound_filespecificationtemplate_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundFilespecificationtemplateProxy
// Type definitions for each func on our proxy, so we can easily mock them out later
type createOutboundFilespecificationtemplateFunc func(ctx context.Context, p *outboundFilespecificationtemplateProxy, fileSpecificationTemplate *platformclientv2.Filespecificationtemplate) (*platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error)
type getAllOutboundFilespecificationtemplateFunc func(ctx context.Context, p *outboundFilespecificationtemplateProxy, name string) (*[]platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error)
type getOutboundFilespecificationtemplateIdByNameFunc func(ctx context.Context, p *outboundFilespecificationtemplateProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getOutboundFilespecificationtemplateByIdFunc func(ctx context.Context, p *outboundFilespecificationtemplateProxy, id string) (fileSpecificationTemplate *platformclientv2.Filespecificationtemplate, response *platformclientv2.APIResponse, err error)
type updateOutboundFilespecificationtemplateFunc func(ctx context.Context, p *outboundFilespecificationtemplateProxy, id string, fileSpecificationTemplate *platformclientv2.Filespecificationtemplate) (*platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error)
type deleteOutboundFilespecificationtemplateFunc func(ctx context.Context, p *outboundFilespecificationtemplateProxy, id string) (response *platformclientv2.APIResponse, err error)
// outboundFilespecificationtemplateProxy contains all the methods that call genesys cloud APIs.
type outboundFilespecificationtemplateProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundFilespecificationtemplateAttr createOutboundFilespecificationtemplateFunc
getAllOutboundFilespecificationtemplateAttr getAllOutboundFilespecificationtemplateFunc
getOutboundFilespecificationtemplateIdByNameAttr getOutboundFilespecificationtemplateIdByNameFunc
getOutboundFilespecificationtemplateByIdAttr getOutboundFilespecificationtemplateByIdFunc
updateOutboundFilespecificationtemplateAttr updateOutboundFilespecificationtemplateFunc
deleteOutboundFilespecificationtemplateAttr deleteOutboundFilespecificationtemplateFunc
}
// newOutboundFilespecificationtemplateProxy initializes the outbound filespecificationtemplate proxy
// with all the data needed to communicate with Genesys Cloud
func newOutboundFilespecificationtemplateProxy(clientConfig *platformclientv2.Configuration) *outboundFilespecificationtemplateProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundFilespecificationtemplateProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundFilespecificationtemplateAttr: createOutboundFilespecificationtemplateFn,
getAllOutboundFilespecificationtemplateAttr: getAllOutboundFilespecificationtemplateFn,
getOutboundFilespecificationtemplateIdByNameAttr: getOutboundFilespecificationtemplateIdByNameFn,
getOutboundFilespecificationtemplateByIdAttr: getOutboundFilespecificationtemplateByIdFn,
updateOutboundFilespecificationtemplateAttr: updateOutboundFilespecificationtemplateFn,
deleteOutboundFilespecificationtemplateAttr: deleteOutboundFilespecificationtemplateFn,
}
}
// getOutboundFilespecificationtemplateProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundFilespecificationtemplateProxy(clientConfig *platformclientv2.Configuration) *outboundFilespecificationtemplateProxy {
if internalProxy == nil {
internalProxy = newOutboundFilespecificationtemplateProxy(clientConfig)
}
return internalProxy
}
// createOutboundFilespecificationtemplate creates a Genesys Cloud outbound filespecificationtemplate
func (p *outboundFilespecificationtemplateProxy) createOutboundFilespecificationtemplate(ctx context.Context, outboundFilespecificationtemplate *platformclientv2.Filespecificationtemplate) (*platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error) {
return p.createOutboundFilespecificationtemplateAttr(ctx, p, outboundFilespecificationtemplate)
}
// getAllOutboundFilespecificationtemplate retrieves all Genesys Cloud outbound filespecificationtemplate
func (p *outboundFilespecificationtemplateProxy) getAllOutboundFilespecificationtemplate(ctx context.Context) (*[]platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error) {
return p.getAllOutboundFilespecificationtemplateAttr(ctx, p, "")
}
// getOutboundFilespecificationtemplateIdByName returns a single Genesys Cloud outbound filespecificationtemplate by name
func (p *outboundFilespecificationtemplateProxy) getOutboundFilespecificationtemplateIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundFilespecificationtemplateIdByNameAttr(ctx, p, name)
}
// getOutboundFilespecificationtemplateById returns a single Genesys Cloud outbound filespecificationtemplate by id
func (p *outboundFilespecificationtemplateProxy) getOutboundFilespecificationtemplateById(ctx context.Context, id string) (outboundFilespecificationtemplate *platformclientv2.Filespecificationtemplate, response *platformclientv2.APIResponse, err error) {
return p.getOutboundFilespecificationtemplateByIdAttr(ctx, p, id)
}
// updateOutboundFilespecificationtemplate updates a Genesys Cloud outbound filespecificationtemplate
func (p *outboundFilespecificationtemplateProxy) updateOutboundFilespecificationtemplate(ctx context.Context, id string, outboundFilespecificationtemplate *platformclientv2.Filespecificationtemplate) (*platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error) {
return p.updateOutboundFilespecificationtemplateAttr(ctx, p, id, outboundFilespecificationtemplate)
}
// deleteOutboundFilespecificationtemplate deletes a Genesys Cloud outbound filespecificationtemplate by id
func (p *outboundFilespecificationtemplateProxy) deleteOutboundFilespecificationtemplate(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundFilespecificationtemplateAttr(ctx, p, id)
}
// createOutboundFilespecificationtemplateFn is an implementation function
// for creating a Genesys Cloud outbound filespecificationtemplate
func createOutboundFilespecificationtemplateFn(ctx context.Context, p *outboundFilespecificationtemplateProxy, outboundFilespecificationtemplate *platformclientv2.Filespecificationtemplate) (*platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error) {
fst, resp, err := p.outboundApi.PostOutboundFilespecificationtemplates(*outboundFilespecificationtemplate)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create file specification template %s", err)
}
return fst, resp, nil
}
// getAllOutboundFilespecificationtemplateFn is the implementation for retrieving
// all outbound filespecificationtemplate in Genesys Cloud
func getAllOutboundFilespecificationtemplateFn(ctx context.Context, p *outboundFilespecificationtemplateProxy, name string) (*[]platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error) {
var allFileSpecificationTemplates []platformclientv2.Filespecificationtemplate
const pageSize = 100
fileSpecificationTemplates, resp, err := p.outboundApi.GetOutboundFilespecificationtemplates(
pageSize, 1, true, "", name, "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get file specification templates: %v", err)
}
if fileSpecificationTemplates.Entities == nil || len(*fileSpecificationTemplates.Entities) == 0 {
return &allFileSpecificationTemplates, resp, nil
}
for _, fileSpecificationTemplate := range *fileSpecificationTemplates.Entities {
allFileSpecificationTemplates = append(allFileSpecificationTemplates, fileSpecificationTemplate)
}
for pageNum := 2; pageNum <= *fileSpecificationTemplates.PageCount; pageNum++ {
fileSpecificationTemplates, resp, err := p.outboundApi.GetOutboundFilespecificationtemplates(
pageSize, pageNum, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get file specification templates: %v", err)
}
if fileSpecificationTemplates.Entities == nil || len(*fileSpecificationTemplates.Entities) == 0 {
break
}
for _, fileSpecificationTemplate := range *fileSpecificationTemplates.Entities {
allFileSpecificationTemplates = append(allFileSpecificationTemplates, fileSpecificationTemplate)
}
}
return &allFileSpecificationTemplates, resp, nil
}
// getOutboundFilespecificationtemplateIdByNameFn is an implementation of the function
// to get a Genesys Cloud outbound filespecificationtemplate by name
func getOutboundFilespecificationtemplateIdByNameFn(ctx context.Context, p *outboundFilespecificationtemplateProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
fileSpecificationTemplates, resp, err := getAllOutboundFilespecificationtemplateFn(ctx, p, name)
if err != nil {
return "", false, resp, err
}
if len(*fileSpecificationTemplates) == 0 {
return "", true, resp, fmt.Errorf("No file specification template found with name %s", name)
}
for _, fileSpecificationTemplate := range *fileSpecificationTemplates {
if *fileSpecificationTemplate.Name == name {
log.Printf("Retrieved the outbound file specification template id %s by name %s", *fileSpecificationTemplate.Id, name)
return *fileSpecificationTemplate.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find outbound file specification template with name %s", name)
}
// getOutboundFilespecificationtemplateByIdFn is an implementation of the function
// to get a Genesys Cloud outbound filespecificationtemplate by id
func getOutboundFilespecificationtemplateByIdFn(ctx context.Context, p *outboundFilespecificationtemplateProxy, id string) (outboundFilespecificationtemplate *platformclientv2.Filespecificationtemplate, response *platformclientv2.APIResponse, err error) {
fst, resp, err := p.outboundApi.GetOutboundFilespecificationtemplate(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve file specification template by id %s: %s", id, err)
}
return fst, resp, nil
}
// updateOutboundFilespecificationtemplateFn is an implementation of the function
// to update a Genesys Cloud outbound filespecificationtemplate
func updateOutboundFilespecificationtemplateFn(ctx context.Context, p *outboundFilespecificationtemplateProxy, id string, outboundFilespecificationtemplate *platformclientv2.Filespecificationtemplate) (*platformclientv2.Filespecificationtemplate, *platformclientv2.APIResponse, error) {
fst, resp, err := getOutboundFilespecificationtemplateByIdFn(ctx, p, id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to file specification template by id %s: %s", id, err)
}
outboundFilespecificationtemplate.Version = fst.Version
fileSpecificationTemplate, resp, err := p.outboundApi.PutOutboundFilespecificationtemplate(id, *outboundFilespecificationtemplate)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update file specification template: %s", err)
}
return fileSpecificationTemplate, resp, nil
}
// deleteOutboundFilespecificationtemplateFn is an implementation function for
// deleting a Genesys Cloud outbound filespecificationtemplate
func deleteOutboundFilespecificationtemplateFn(ctx context.Context, p *outboundFilespecificationtemplateProxy, id string) (response *platformclientv2.APIResponse, err error) {
return p.outboundApi.DeleteOutboundFilespecificationtemplate(id)
}
package outbound_filespecificationtemplate
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllFileSpecificationTemplates(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getOutboundFilespecificationtemplateProxy(clientConfig)
fileSpecificationTemplates, resp, err := proxy.getAllOutboundFilespecificationtemplate(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get outbound file specification templates error: %s", err), resp)
}
for _, fst := range *fileSpecificationTemplates {
resources[*fst.Id] = &resourceExporter.ResourceMeta{Name: *fst.Name}
}
return resources, nil
}
func createOutboundFileSpecificationTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkFileSpecificationTemplate := getFilespecificationtemplateFromResourceData(d)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundFilespecificationtemplateProxy(sdkConfig)
log.Printf("Creating File Specification Template %s", *sdkFileSpecificationTemplate.Name)
outboundFileSpecificationTemplate, resp, err := proxy.createOutboundFilespecificationtemplate(ctx, &sdkFileSpecificationTemplate)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create File Specification Template %s error: %s", *sdkFileSpecificationTemplate.Name, err), resp)
}
d.SetId(*outboundFileSpecificationTemplate.Id)
log.Printf("Created File Specification Template %s %s", *outboundFileSpecificationTemplate.Name, *outboundFileSpecificationTemplate.Id)
return readOutboundFileSpecificationTemplate(ctx, d, meta)
}
func updateOutboundFileSpecificationTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkFileSpecificationTemplate := getFilespecificationtemplateFromResourceData(d)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundFilespecificationtemplateProxy(sdkConfig)
log.Printf("Updating File Specification Template %s", *sdkFileSpecificationTemplate.Name)
_, resp, err := proxy.updateOutboundFilespecificationtemplate(ctx, d.Id(), &sdkFileSpecificationTemplate)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update File Specification Template%s error: %s", d.Id(), err), resp)
}
log.Printf("Updated Outbound File Specification Template %s", *sdkFileSpecificationTemplate.Name)
return readOutboundFileSpecificationTemplate(ctx, d, meta)
}
func readOutboundFileSpecificationTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundFilespecificationtemplateProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundFileSpecificationTemplate(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound File Specification Template %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkFileSpecificationTemplate, resp, getErr := proxy.getOutboundFilespecificationtemplateById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound File Specification Template %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound File Specification Template %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", sdkFileSpecificationTemplate.Name)
resourcedata.SetNillableValue(d, "description", sdkFileSpecificationTemplate.Description)
resourcedata.SetNillableValue(d, "format", sdkFileSpecificationTemplate.Format)
resourcedata.SetNillableValue(d, "number_of_header_lines_skipped", sdkFileSpecificationTemplate.NumberOfHeadingLinesSkipped)
resourcedata.SetNillableValue(d, "number_of_trailer_lines_skipped", sdkFileSpecificationTemplate.NumberOfTrailingLinesSkipped)
resourcedata.SetNillableValue(d, "header", sdkFileSpecificationTemplate.Header)
resourcedata.SetNillableValue(d, "delimiter", sdkFileSpecificationTemplate.Delimiter)
resourcedata.SetNillableValue(d, "delimiter_value", sdkFileSpecificationTemplate.DelimiterValue)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "column_information", sdkFileSpecificationTemplate.ColumnInformation, flattenSdkOutboundFileSpecificationTemplateColumnInformationSlice)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "preprocessing_rule", sdkFileSpecificationTemplate.PreprocessingRules, flattenSdkOutboundFileSpecificationTemplatePreprocessingRulesSlice)
log.Printf("Read Outbound File Specification Template %s %s", d.Id(), *sdkFileSpecificationTemplate.Name)
return cc.CheckState(d)
})
}
func deleteOutboundFileSpecificationTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundFilespecificationtemplateProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Outbound File Specification Template")
resp, err := proxy.deleteOutboundFilespecificationtemplate(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound File Specification Template %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getOutboundFilespecificationtemplateById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// File Specification Template List deleted
log.Printf("Deleted Outbound File Specification Template %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound File Specification Template %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound File Specification Template %s still exists", d.Id()), resp))
})
}
package outbound_filespecificationtemplate
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_outbound_filespecificationtemplate"
var (
outboundFileSpecificationTemplateColumnInformationResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`column_name`: {
Description: `Column name. Mandatory for Fixed position/length file format.`,
Optional: true,
Type: schema.TypeString,
},
`column_number`: {
Description: `0 based column number in delimited file format.`,
Optional: true,
Type: schema.TypeInt,
},
`start_position`: {
Description: `Zero-based position of the first column's character. Mandatory for Fixed position/length file format.`,
Optional: true,
Type: schema.TypeInt,
},
`length`: {
Description: `Column width. Mandatory for Fixed position/length file format.`,
Optional: true,
Type: schema.TypeInt,
},
},
}
outboundFileSpecificationTemplatePreprocessingRuleResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`find`: {
Description: `The regular expression to which file lines are to be matched`,
Optional: true,
Type: schema.TypeString,
},
`replace_with`: {
Description: `The string to be substituted for each match.`,
Optional: true,
Type: schema.TypeString,
},
`global`: {
Description: `Replaces all matching substrings in every line.`,
Optional: true,
Type: schema.TypeBool,
},
`ignore_case`: {
Description: `Enables case-insensitive matching.`,
Optional: true,
Type: schema.TypeBool,
},
},
}
)
func ResourceOutboundFileSpecificationTemplate() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Outbound File Specification Template`,
CreateContext: provider.CreateWithPooledClient(createOutboundFileSpecificationTemplate),
ReadContext: provider.ReadWithPooledClient(readOutboundFileSpecificationTemplate),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundFileSpecificationTemplate),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundFileSpecificationTemplate),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the File Specification template.`,
Required: true,
Type: schema.TypeString,
},
`description`: {
Description: `Description of the file specification template`,
Optional: true,
Type: schema.TypeString,
},
`format`: {
Description: `File format`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"FixedLength", "Delimited"}, false),
},
`number_of_header_lines_skipped`: {
Description: `Number of heading lines to be skipped`,
Optional: true,
Type: schema.TypeInt,
},
`number_of_trailer_lines_skipped`: {
Description: `Number of trailing lines to be skipped`,
Optional: true,
Type: schema.TypeInt,
},
`header`: {
Description: `If true indicates that delimited file has a header row, which can provide column names`,
Optional: true,
Type: schema.TypeBool,
},
`delimiter`: {
Description: `Kind of delimiter`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"Comma", "Pipe", "Colon", "Tab", "Semicolon", "Custom"}, false),
Default: "Comma",
},
`delimiter_value`: {
Description: `Delimiter character, used only when delimiter="Custom"`,
Optional: true,
Type: schema.TypeString,
},
`column_information`: {
Description: `Columns specification`,
Optional: true,
Type: schema.TypeList,
Elem: outboundFileSpecificationTemplateColumnInformationResource,
},
`preprocessing_rule`: {
Description: `Preprocessing rule`,
Optional: true,
Type: schema.TypeList,
Elem: outboundFileSpecificationTemplatePreprocessingRuleResource,
},
},
}
}
func dataSourceOutboundFileSpecificationTemplate() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Outbound File Specification Template. Select a file specification template by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundFileSpecificationTemplateRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "File Specification Template name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, dataSourceOutboundFileSpecificationTemplate())
l.RegisterResource(resourceName, ResourceOutboundFileSpecificationTemplate())
l.RegisterExporter(resourceName, OutboundFileSpecificationTemplateExporter())
}
func OutboundFileSpecificationTemplateExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllFileSpecificationTemplates),
}
}
package outbound_filespecificationtemplate
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
func getFilespecificationtemplateFromResourceData(d *schema.ResourceData) platformclientv2.Filespecificationtemplate {
description := d.Get("description").(string)
delimiter := d.Get("delimiter").(string)
sdkFileSpecificationTemplate := platformclientv2.Filespecificationtemplate{
Name: platformclientv2.String(d.Get("name").(string)),
Format: platformclientv2.String(d.Get("format").(string)),
NumberOfHeadingLinesSkipped: platformclientv2.Int(d.Get("number_of_header_lines_skipped").(int)),
NumberOfTrailingLinesSkipped: platformclientv2.Int(d.Get("number_of_trailer_lines_skipped").(int)),
Header: platformclientv2.Bool(d.Get("header").(bool)),
DelimiterValue: platformclientv2.String(d.Get("delimiter_value").(string)),
ColumnInformation: buildSdkOutboundFileSpecificationTemplateColumnInformationSlice(d.Get("column_information").([]interface{})),
PreprocessingRules: buildSdkOutboundFileSpecificationTemplatePreprocessingRulesSlice(d.Get("preprocessing_rule").([]interface{})),
}
if description != "" {
sdkFileSpecificationTemplate.Description = &description
}
if delimiter != "" {
sdkFileSpecificationTemplate.Delimiter = &delimiter
}
return sdkFileSpecificationTemplate
}
func buildSdkOutboundFileSpecificationTemplateColumnInformationSlice(columnInformation []interface{}) *[]platformclientv2.Column {
if columnInformation == nil || len(columnInformation) < 1 {
return nil
}
sdkColumnInformationSlice := make([]platformclientv2.Column, 0)
for _, columnInfo := range columnInformation {
if columnInfoMap, ok := columnInfo.(map[string]interface{}); ok {
var sdkColumnInformation platformclientv2.Column
resourcedata.BuildSDKStringValueIfNotNil(&sdkColumnInformation.ColumnName, columnInfoMap, "column_name")
if columnNumberInt, ok := columnInfoMap["column_number"].(int); ok {
sdkColumnInformation.ColumnNumber = platformclientv2.Int(columnNumberInt)
}
if startPositionInt, ok := columnInfoMap["start_position"].(int); ok {
sdkColumnInformation.StartPosition = platformclientv2.Int(startPositionInt)
}
if lengthInt, ok := columnInfoMap["length"].(int); ok {
sdkColumnInformation.Length = platformclientv2.Int(lengthInt)
}
sdkColumnInformationSlice = append(sdkColumnInformationSlice, sdkColumnInformation)
}
}
return &sdkColumnInformationSlice
}
func buildSdkOutboundFileSpecificationTemplatePreprocessingRulesSlice(preprocessingRules []interface{}) *[]platformclientv2.Preprocessingrule {
if preprocessingRules == nil || len(preprocessingRules) < 1 {
return nil
}
sdkPreprocessingRulesSlice := make([]platformclientv2.Preprocessingrule, 0)
for _, preprocessingRule := range preprocessingRules {
if preprocessingRuleMap, ok := preprocessingRule.(map[string]interface{}); ok {
var sdkPreprocessingRule platformclientv2.Preprocessingrule
resourcedata.BuildSDKStringValueIfNotNil(&sdkPreprocessingRule.Find, preprocessingRuleMap, "find")
resourcedata.BuildSDKStringValueIfNotNil(&sdkPreprocessingRule.ReplaceWith, preprocessingRuleMap, "replace_with")
if isGlobal, ok := preprocessingRuleMap["global"].(bool); ok {
sdkPreprocessingRule.Global = platformclientv2.Bool(isGlobal)
}
if isIgnoreCase, ok := preprocessingRuleMap["ignore_case"].(bool); ok {
sdkPreprocessingRule.IgnoreCase = platformclientv2.Bool(isIgnoreCase)
}
sdkPreprocessingRulesSlice = append(sdkPreprocessingRulesSlice, sdkPreprocessingRule)
}
}
return &sdkPreprocessingRulesSlice
}
func flattenSdkOutboundFileSpecificationTemplateColumnInformationSlice(fileSpecificationTemplateColumnInformation *[]platformclientv2.Column) []interface{} {
if len(*fileSpecificationTemplateColumnInformation) == 0 {
return nil
}
columnInformationList := make([]interface{}, 0)
for _, columnInformation := range *fileSpecificationTemplateColumnInformation {
columnInformationMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(columnInformationMap, "column_name", columnInformation.ColumnName)
resourcedata.SetMapValueIfNotNil(columnInformationMap, "column_number", columnInformation.ColumnNumber)
resourcedata.SetMapValueIfNotNil(columnInformationMap, "start_position", columnInformation.StartPosition)
resourcedata.SetMapValueIfNotNil(columnInformationMap, "length", columnInformation.Length)
columnInformationList = append(columnInformationList, columnInformationMap)
}
return columnInformationList
}
func flattenSdkOutboundFileSpecificationTemplatePreprocessingRulesSlice(fileSpecificationTemplatePreprocessingRules *[]platformclientv2.Preprocessingrule) []interface{} {
if len(*fileSpecificationTemplatePreprocessingRules) == 0 {
return nil
}
preprocessingRulesList := make([]interface{}, 0)
for _, preprocessingRule := range *fileSpecificationTemplatePreprocessingRules {
preprocessingRuleMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(preprocessingRuleMap, "find", preprocessingRule.Find)
resourcedata.SetMapValueIfNotNil(preprocessingRuleMap, "replace_with", preprocessingRule.ReplaceWith)
resourcedata.SetMapValueIfNotNil(preprocessingRuleMap, "global", preprocessingRule.Global)
resourcedata.SetMapValueIfNotNil(preprocessingRuleMap, "ignore_case", preprocessingRule.IgnoreCase)
preprocessingRulesList = append(preprocessingRulesList, preprocessingRuleMap)
}
return preprocessingRulesList
}
package outbound_ruleset
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_outbound_ruleset.go contains the data source implementation
for the resource.
*/
// dataSourceOutboundRulesetRead retrieves by name the id in question
func dataSourceOutboundRulesetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundRulesetProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
rulesetId, retryable, resp, err := proxy.getOutboundRulesetIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error ruleset %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No ruleset found with name %s", name), resp))
}
d.SetId(rulesetId)
return nil
})
}
package outbound_ruleset
import (
"context"
"fmt"
"log"
"strings"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_outbound_ruleset_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundRulesetProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOutboundRulesetFunc func(ctx context.Context, p *outboundRulesetProxy, ruleset *platformclientv2.Ruleset) (*platformclientv2.Ruleset, *platformclientv2.APIResponse, error)
type getAllOutboundRulesetFunc func(ctx context.Context, p *outboundRulesetProxy) (*[]platformclientv2.Ruleset, *platformclientv2.APIResponse, error)
type getOutboundRulesetByIdFunc func(ctx context.Context, p *outboundRulesetProxy, rulesetId string) (ruleset *platformclientv2.Ruleset, response *platformclientv2.APIResponse, err error)
type getOutboundRulesetIdByNameFunc func(ctx context.Context, p *outboundRulesetProxy, search string) (rulesetId string, retryable bool, response *platformclientv2.APIResponse, err error)
type updateOutboundRulesetFunc func(ctx context.Context, p *outboundRulesetProxy, rulesetId string, ruleset *platformclientv2.Ruleset) (*platformclientv2.Ruleset, *platformclientv2.APIResponse, error)
type deleteOutboundRulesetFunc func(ctx context.Context, p *outboundRulesetProxy, rulesetId string) (response *platformclientv2.APIResponse, err error)
// outboundRulesetProxy contains all of the methods that call genesys cloud APIs.
type outboundRulesetProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundRulesetAttr createOutboundRulesetFunc
getAllOutboundRulesetAttr getAllOutboundRulesetFunc
getOutboundRulesetByIdAttr getOutboundRulesetByIdFunc
getOutboundRulesetIdByNameAttr getOutboundRulesetIdByNameFunc
updateOutboundRulesetAttr updateOutboundRulesetFunc
deleteOutboundRulesetAttr deleteOutboundRulesetFunc
}
// newOutboundRulesetProxy initializes the ruleset proxy with all of the data needed to communicate with Genesys Cloud
func newOutboundRulesetProxy(clientConfig *platformclientv2.Configuration) *outboundRulesetProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundRulesetProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundRulesetAttr: createOutboundRulesetFn,
getAllOutboundRulesetAttr: getAllOutboundRulesetFn,
getOutboundRulesetByIdAttr: getOutboundRulesetByIdFn,
getOutboundRulesetIdByNameAttr: getOutboundRulesetIdByNameFn,
updateOutboundRulesetAttr: updateOutboundRulesetFn,
deleteOutboundRulesetAttr: deleteOutboundRulesetFn,
}
}
// getOutboundRulesetProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundRulesetProxy(clientConfig *platformclientv2.Configuration) *outboundRulesetProxy {
if internalProxy == nil {
internalProxy = newOutboundRulesetProxy(clientConfig)
}
return internalProxy
}
// createOutboundRuleset creates a Genesys Cloud Outbound Ruleset
func (p *outboundRulesetProxy) createOutboundRuleset(ctx context.Context, ruleset *platformclientv2.Ruleset) (*platformclientv2.Ruleset, *platformclientv2.APIResponse, error) {
return p.createOutboundRulesetAttr(ctx, p, ruleset)
}
// getOutboundRuleset retrieves all Genesys Cloud Outbound Ruleset
func (p *outboundRulesetProxy) getAllOutboundRuleset(ctx context.Context) (*[]platformclientv2.Ruleset, *platformclientv2.APIResponse, error) {
return p.getAllOutboundRulesetAttr(ctx, p)
}
// getOutboundRulesetById returns a single Genesys Cloud Outbound Ruleset by Id
func (p *outboundRulesetProxy) getOutboundRulesetById(ctx context.Context, rulesetId string) (ruleset *platformclientv2.Ruleset, response *platformclientv2.APIResponse, err error) {
return p.getOutboundRulesetByIdAttr(ctx, p, rulesetId)
}
// getOutboundRulesetIdByName returns a single Genesys Cloud Outbound Ruleset by a name
func (p *outboundRulesetProxy) getOutboundRulesetIdByName(ctx context.Context, name string) (rulesetId string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundRulesetIdByNameAttr(ctx, p, name)
}
// updateOutboundRuleset updates a Genesys Cloud Outbound Ruleset
func (p *outboundRulesetProxy) updateOutboundRuleset(ctx context.Context, rulesetId string, ruleset *platformclientv2.Ruleset) (*platformclientv2.Ruleset, *platformclientv2.APIResponse, error) {
return p.updateOutboundRulesetAttr(ctx, p, rulesetId, ruleset)
}
// deleteOutboundRuleset deletes a Genesys Cloud Outbound Ruleset by Id
func (p *outboundRulesetProxy) deleteOutboundRuleset(ctx context.Context, rulesetId string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundRulesetAttr(ctx, p, rulesetId)
}
// createOutboundRulesetFn is an implementation function for creating a Genesys Cloud Outbound Ruleset
func createOutboundRulesetFn(ctx context.Context, p *outboundRulesetProxy, ruleset *platformclientv2.Ruleset) (*platformclientv2.Ruleset, *platformclientv2.APIResponse, error) {
ruleset, resp, err := p.outboundApi.PostOutboundRulesets(*ruleset)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create ruleset: %s", err)
}
return ruleset, resp, nil
}
// getAllOutboundRulesetFn is the implementation for retrieving all outbound ruleset in Genesys Cloud
func getAllOutboundRulesetFn(ctx context.Context, p *outboundRulesetProxy) (*[]platformclientv2.Ruleset, *platformclientv2.APIResponse, error) {
var allRulesets []platformclientv2.Ruleset
var response *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
rulesets, resp, err := p.outboundApi.GetOutboundRulesets(pageSize, pageNum, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get outbound rulesets: %v", err)
}
response = resp
if rulesets.Entities == nil || len(*rulesets.Entities) == 0 {
break
}
for _, ruleset := range *rulesets.Entities {
log.Printf("Dealing with ruleset id : %s", *ruleset.Id)
allRulesets = append(allRulesets, ruleset)
}
}
return &allRulesets, response, nil
}
// getOutboundRulesetByIdFn is an implementation of the function to get a Genesys Cloud Outbound Ruleset by Id
func getOutboundRulesetByIdFn(ctx context.Context, p *outboundRulesetProxy, rulesetId string) (ruleset *platformclientv2.Ruleset, response *platformclientv2.APIResponse, err error) {
ruleset, resp, err := p.outboundApi.GetOutboundRuleset(rulesetId)
if err != nil {
//This is an API that throws an error on a 404 instead of just returning a 404.
if strings.Contains(fmt.Sprintf("%s", err), "API Error: 404") {
return nil, resp, nil
}
return nil, resp, fmt.Errorf("Failed to retrieve ruleset by id %s: %s", rulesetId, err)
}
return ruleset, resp, nil
}
// getOutboundRulesetIdBySearchFn is an implementation of the function to get a Genesys Cloud Outbound Ruleset by name
func getOutboundRulesetIdByNameFn(ctx context.Context, p *outboundRulesetProxy, name string) (rulesetId string, retryable bool, response *platformclientv2.APIResponse, err error) {
const pageNum = 1
const pageSize = 100
rulesets, resp, err := p.outboundApi.GetOutboundRulesets(pageSize, pageNum, true, "", name, "", "")
if err != nil {
return "", false, resp, fmt.Errorf("Error searching outbound ruleset %s: %s", name, err)
}
if rulesets.Entities == nil || len(*rulesets.Entities) == 0 {
return "", true, resp, fmt.Errorf("No outbound ruleset found with name %s", name)
}
var ruleset platformclientv2.Ruleset
entities := *rulesets.Entities
for _, rulesetSdk := range entities {
if *rulesetSdk.Name == name {
log.Printf("Retrieved the ruleset id %s by name %s", *rulesetSdk.Id, name)
ruleset = rulesetSdk
return *ruleset.Id, false, resp, nil
}
}
return "", false, resp, fmt.Errorf("Unable to find ruleset with name %s", name)
}
// updateOutboundRulesetFn is an implementation of the function to update a Genesys Cloud Outbound Rulesets
func updateOutboundRulesetFn(ctx context.Context, p *outboundRulesetProxy, rulesetId string, ruleset *platformclientv2.Ruleset) (*platformclientv2.Ruleset, *platformclientv2.APIResponse, error) {
outboundRuleset, resp, err := getOutboundRulesetByIdFn(ctx, p, rulesetId)
if err != nil {
return nil, resp, fmt.Errorf("Failed to ruleset by id %s: %s", rulesetId, err)
}
ruleset.Version = outboundRuleset.Version
ruleset, resp, err = p.outboundApi.PutOutboundRuleset(rulesetId, *ruleset)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update ruleset: %s", err)
}
return ruleset, resp, nil
}
// deleteOutboundRulesetFn is an implementation function for deleting a Genesys Cloud Outbound Rulesets
func deleteOutboundRulesetFn(ctx context.Context, p *outboundRulesetProxy, rulesetId string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.outboundApi.DeleteOutboundRuleset(rulesetId)
if err != nil {
return resp, fmt.Errorf("Failed to delete ruleset: %s", err)
}
return resp, nil
}
package outbound_ruleset
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
gcloud "terraform-provider-genesyscloud/genesyscloud"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_outbound_ruleset.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthOutboundRulesets retrieves all of the outbound rulesets via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthOutboundRuleset(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getOutboundRulesetProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
rulesets, resp, rsErr := proxy.getAllOutboundRuleset(ctx)
if rsErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get rulesets error: %s", rsErr), resp)
}
// DEVTOOLING-319: filters rule sets by removing the ones that reference skills that no longer exist in GC
skillExporter := gcloud.RoutingSkillExporter()
skillMap, skillErr := skillExporter.GetResourcesFunc(ctx)
if skillErr != nil {
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to get skill resources"), fmt.Errorf("%v", skillErr))
}
filteredRuleSets, filterErr := filterOutboundRulesets(*rulesets, skillMap)
if filterErr != nil {
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to filter outbound rulesets"), fmt.Errorf("%v", filterErr))
}
for _, ruleset := range filteredRuleSets {
log.Printf("Dealing with ruleset id : %s", *ruleset.Id)
resources[*ruleset.Id] = &resourceExporter.ResourceMeta{Name: *ruleset.Name}
}
return resources, nil
}
// createOutboundRuleset is used by the outbound_ruleset resource to create Genesys cloud outbound_ruleset
func createOutboundRuleset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundRulesetProxy(sdkConfig)
outboundRuleset := getOutboundRulesetFromResourceData(d)
ruleset, resp, err := proxy.createOutboundRuleset(ctx, &outboundRuleset)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create ruleset %s error: %s", *outboundRuleset.Name, err), resp)
}
d.SetId(*ruleset.Id)
log.Printf("Created Outbound Ruleset %s", *ruleset.Id)
return readOutboundRuleset(ctx, d, meta)
}
// readOutboundRuleset is used by the outbound_ruleset resource to read an outbound ruleset from genesys cloud.
func readOutboundRuleset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundRulesetProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundRuleset(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Ruleset %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
ruleset, resp, getErr := proxy.getOutboundRulesetById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Ruleset %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Ruleset %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", ruleset.Name)
resourcedata.SetNillableReference(d, "contact_list_id", ruleset.ContactList)
resourcedata.SetNillableReference(d, "queue_id", ruleset.Queue)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "rules", ruleset.Rules, flattenDialerrules)
log.Printf("Read Outbound Ruleset %s %s", d.Id(), *ruleset.Name)
return cc.CheckState(d)
})
}
// updateOutboundRuleset is used by the outbound_ruleset resource to update an outbound ruleset in Genesys Cloud
func updateOutboundRuleset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundRulesetProxy(sdkConfig)
outboundRuleset := getOutboundRulesetFromResourceData(d)
ruleset, resp, err := proxy.updateOutboundRuleset(ctx, d.Id(), &outboundRuleset)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update ruleset %s error: %s", *outboundRuleset.Name, err), resp)
}
log.Printf("Updated Outbound Ruleset %s", *ruleset.Id)
return readOutboundRuleset(ctx, d, meta)
}
// deleteOutboundRuleset is used by the outbound_ruleset resource to delete an outbound ruleset from Genesys cloud.
func deleteOutboundRuleset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundRulesetProxy(sdkConfig)
resp, err := proxy.deleteOutboundRuleset(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete ruleset %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 1800*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getOutboundRulesetById(ctx, d.Id())
//Now that I am checking for th error string of API 404 and there is no error, I need to move the isStatus404
//moved out of the code
if util.IsStatus404(resp) {
// Outbound Ruleset deleted
log.Printf("Deleted Outbound Ruleset %s", d.Id())
return nil
}
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Outbound Ruleset %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Ruleset %s still exists", d.Id()), resp))
})
}
// filterOutboundRulesets filters rule sets by removing the ones that reference skills that no longer exist in GC
func filterOutboundRulesets(ruleSets []platformclientv2.Ruleset, skillMap resourceExporter.ResourceIDMetaMap) ([]platformclientv2.Ruleset, diag.Diagnostics) {
var filteredRuleSets []platformclientv2.Ruleset
log.Printf("Filtering outbound rule sets")
for _, ruleSet := range ruleSets {
var foundDeleted bool
for _, rule := range *ruleSet.Rules {
if doesRuleActionsRefDeletedSkill(rule, skillMap) || doesRuleConditionsRefDeletedSkill(rule, skillMap) {
foundDeleted = true
break
}
}
if foundDeleted {
log.Printf("Removing ruleset id '%s'", *ruleSet.Id)
} else {
// No references to a deleted skill in the ruleset, keep it
filteredRuleSets = append(filteredRuleSets, ruleSet)
}
}
return filteredRuleSets, nil
}
package outbound_ruleset
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesyscloud_outbound_ruleset_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the outbound_ruleset resource.
3. The datasource schema definitions for the outbound_ruleset datasource.
4. The resource exporter configuration for the outbound_ruleset exporter.
*/
const resourceName = "genesyscloud_outbound_ruleset"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceOutboundRuleset())
regInstance.RegisterDataSource(resourceName, DataSourceOutboundRuleset())
regInstance.RegisterExporter(resourceName, OutboundRulesetExporter())
}
// ResourceOutboundRuleset registers the genesyscloud_outbound_ruleset resource with Terraform
func ResourceOutboundRuleset() *schema.Resource {
outboundrulesetcontactcolumntodataactionfieldmappingResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`contact_column_name`: {
Description: `The name of a contact column whose data will be passed to the data action`,
Required: true,
Type: schema.TypeString,
},
`data_action_field`: {
Description: `The name of an input field from the data action that the contact column data will be passed to`,
Required: true,
Type: schema.TypeString,
},
},
}
outboundrulesetdataactionconditionpredicateResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`output_field`: {
Description: `The name of an output field from the data action's output to use for this condition`,
Required: true,
Type: schema.TypeString,
},
`output_operator`: {
Description: `The operation with which to evaluate this condition`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`EQUALS`, `LESS_THAN`, `LESS_THAN_EQUALS`, `GREATER_THAN`, `GREATER_THAN_EQUALS`, `CONTAINS`, `BEGINS_WITH`, `ENDS_WITH`, `BEFORE`, `AFTER`}, false),
},
`comparison_value`: {
Description: `The value to compare against for this condition`,
Required: true,
Type: schema.TypeString,
},
`inverted`: {
Description: `If true, inverts the result of evaluating this Predicate. Default is false.`,
Required: true,
Type: schema.TypeBool,
},
`output_field_missing_resolution`: {
Description: `The result of this predicate if the requested output field is missing from the data action's result`,
Required: true,
Type: schema.TypeBool,
},
},
}
outboundrulesetconditionResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`type`: {
Description: `The type of the condition.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`wrapupCondition`, `systemDispositionCondition`, `contactAttributeCondition`, `phoneNumberCondition`, `phoneNumberTypeCondition`, `callAnalysisCondition`, `contactPropertyCondition`, `dataActionCondition`}, false),
},
`inverted`: {
Description: `If true, inverts the result of evaluating this Condition. Default is false.`,
Optional: true,
Type: schema.TypeBool,
},
`attribute_name`: {
Description: `An attribute name associated with this Condition. Required for a contactAttributeCondition.`,
Optional: true,
Type: schema.TypeString,
},
`value`: {
Description: `A value associated with this Condition. This could be text, a number, or a relative time. Not used for a DataActionCondition.`,
Optional: true,
Type: schema.TypeString,
},
`value_type`: {
Description: `The type of the value associated with this Condition. Not used for a DataActionCondition.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`STRING`, `NUMERIC`, `DATETIME`, `PERIOD`}, false),
},
`operator`: {
Description: `An operation with which to evaluate the Condition. Not used for a DataActionCondition.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`EQUALS`, `LESS_THAN`, `LESS_THAN_EQUALS`, `GREATER_THAN`, `GREATER_THAN_EQUALS`, `CONTAINS`, `BEGINS_WITH`, `ENDS_WITH`, `BEFORE`, `AFTER`, `IN`}, false),
},
`codes`: {
Description: `List of wrap-up code identifiers. Required for a wrapupCondition.`,
Optional: true,
Computed: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`property`: {
Description: `A value associated with the property type of this Condition. Required for a contactPropertyCondition.`,
Optional: true,
Type: schema.TypeString,
},
`property_type`: {
Description: `The type of the property associated with this Condition. Required for a contactPropertyCondition.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`LAST_ATTEMPT_BY_COLUMN`, `LAST_ATTEMPT_OVERALL`, `LAST_WRAPUP_BY_COLUMN`, `LAST_WRAPUP_OVERALL`}, false),
},
`data_action_id`: {
Description: `The Data Action to use for this condition. Required for a dataActionCondition.`,
Optional: true,
Type: schema.TypeString,
},
`data_not_found_resolution`: {
Description: `The result of this condition if the data action returns a result indicating there was no data. Required for a DataActionCondition.`,
Optional: true,
Type: schema.TypeBool,
},
`contact_id_field`: {
Description: `The input field from the data action that the contactId will be passed to for this condition. Valid for a dataActionCondition.`,
Optional: true,
Type: schema.TypeString,
},
`call_analysis_result_field`: {
Description: `The input field from the data action that the callAnalysisResult will be passed to for this condition. Valid for a wrapup dataActionCondition.`,
Optional: true,
Type: schema.TypeString,
},
`agent_wrapup_field`: {
Description: `The input field from the data action that the agentWrapup will be passed to for this condition. Valid for a wrapup dataActionCondition.`,
Optional: true,
Type: schema.TypeString,
},
`contact_column_to_data_action_field_mappings`: {
Description: `A list of mappings defining which contact data fields will be passed to which data action input fields for this condition. Valid for a dataActionCondition.`,
Optional: true,
Type: schema.TypeList,
Elem: outboundrulesetcontactcolumntodataactionfieldmappingResource,
},
`predicates`: {
Description: `A list of predicates defining the comparisons to use for this condition. Required for a dataActionCondition.`,
Optional: true,
Type: schema.TypeList,
Elem: outboundrulesetdataactionconditionpredicateResource,
},
},
}
outboundrulesetdialeractionResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`type`: {
Description: `The type of this DialerAction.`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`Action`, `modifyContactAttribute`, `dataActionBehavior`}, false),
},
`action_type_name`: {
Description: `Additional type specification for this DialerAction.`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`DO_NOT_DIAL`, `MODIFY_CONTACT_ATTRIBUTE`, `SWITCH_TO_PREVIEW`, `APPEND_NUMBER_TO_DNC_LIST`, `SCHEDULE_CALLBACK`, `CONTACT_UNCALLABLE`, `NUMBER_UNCALLABLE`, `SET_CALLER_ID`, `SET_SKILLS`, `DATA_ACTION`}, false),
},
`update_option`: {
Description: `Specifies how a contact attribute should be updated. Required for MODIFY_CONTACT_ATTRIBUTE.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`SET`, `INCREMENT`, `DECREMENT`, `CURRENT_TIME`}, false),
},
`properties`: {
Description: `A map of key-value pairs pertinent to the DialerAction. Different types of DialerActions require different properties. MODIFY_CONTACT_ATTRIBUTE with an updateOption of SET takes a contact column as the key and accepts any value. SCHEDULE_CALLBACK takes a key 'callbackOffset' that specifies how far in the future the callback should be scheduled, in minutes. SET_CALLER_ID takes two keys: 'callerAddress', which should be the caller id phone number, and 'callerName'. For either key, you can also specify a column on the contact to get the value from. To do this, specify 'contact.Column', where 'Column' is the name of the contact column from which to get the value. SET_SKILLS takes a key 'skills' with an array of skill ids wrapped into a string (Example: {'skills': '['skillIdHere']'} ).`,
Optional: true,
Computed: true,
Type: schema.TypeMap,
Elem: &schema.Schema{Type: schema.TypeString},
},
`data_action_id`: {
Description: `The Data Action to use for this action. Required for a dataActionBehavior.`,
Optional: true,
Type: schema.TypeString,
},
`contact_column_to_data_action_field_mappings`: {
Description: `A list of mappings defining which contact data fields will be passed to which data action input fields for this condition. Valid for a dataActionBehavior.`,
Optional: true,
Type: schema.TypeList,
Elem: outboundrulesetcontactcolumntodataactionfieldmappingResource,
},
`contact_id_field`: {
Description: `The input field from the data action that the contactId will be passed to for this condition. Valid for a dataActionBehavior.`,
Optional: true,
Type: schema.TypeString,
},
`call_analysis_result_field`: {
Description: `The input field from the data action that the callAnalysisResult will be passed to for this condition. Valid for a wrapup dataActionBehavior.`,
Optional: true,
Type: schema.TypeString,
},
`agent_wrapup_field`: {
Description: `The input field from the data action that the agentWrapup will be passed to for this condition. Valid for a wrapup dataActionBehavior.`,
Optional: true,
Type: schema.TypeString,
},
},
}
outboundrulesetdialerruleResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the rule.`,
Required: true,
Type: schema.TypeString,
},
`order`: {
Description: `The ranked order of the rule. Rules are processed from lowest number to highest.`,
Optional: true,
Type: schema.TypeInt,
},
`category`: {
Description: `The category of the rule.`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`DIALER_PRECALL`, `DIALER_WRAPUP`}, false),
},
`conditions`: {
Description: `A list of Conditions. All of the Conditions must evaluate to true to trigger the actions.`,
Required: true,
Type: schema.TypeList,
Elem: outboundrulesetconditionResource,
},
`actions`: {
Description: `The list of actions to be taken if the conditions are true.`,
Required: true,
Type: schema.TypeList,
Elem: outboundrulesetdialeractionResource,
},
},
}
return &schema.Resource{
Description: `Genesys Cloud outbound ruleset`,
CreateContext: provider.CreateWithPooledClient(createOutboundRuleset),
ReadContext: provider.ReadWithPooledClient(readOutboundRuleset),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundRuleset),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundRuleset),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the RuleSet.`,
Required: true,
Type: schema.TypeString,
},
`contact_list_id`: {
Description: `A ContactList to provide user-interface suggestions for contact columns on relevant conditions and actions.`,
Optional: true,
Type: schema.TypeString,
},
`queue_id`: {
Description: `A Queue to provide user-interface suggestions for wrap-up codes on relevant conditions and actions.`,
Optional: true,
Type: schema.TypeString,
},
`rules`: {
Description: `The list of rules.`,
Optional: true,
Type: schema.TypeList,
Elem: outboundrulesetdialerruleResource,
},
},
}
}
// OutboundRulesetExporter returns the resourceExporter object used to hold the genesyscloud_outbound_ruleset exporter's config
func OutboundRulesetExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthOutboundRuleset),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"contact_list_id": {
RefType: "genesyscloud_outbound_contact_list",
},
"queue_id": {
RefType: "genesyscloud_routing_queue",
},
"rules.conditions.codes": {
RefType: "genesyscloud_routing_wrapupcode",
},
"rules.conditions.data_action_id": {
RefType: "genesyscloud_integration_action",
},
"rules.actions.data_action_id": {
RefType: "genesyscloud_integration_action",
},
},
JsonEncodeAttributes: []string{"rules.actions.properties.skills"},
CustomAttributeResolver: map[string]*resourceExporter.RefAttrCustomResolver{
"rules.actions.properties": {ResolverFunc: resourceExporter.RuleSetPropertyResolver},
"rules.actions.properties.skills": {ResolverFunc: resourceExporter.RuleSetSkillPropertyResolver},
},
}
}
// DataSourceOutboundRuleset registers the genesyscloud_outbound_ruleset data source
func DataSourceOutboundRuleset() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Outbound Ruleset. Select an Outbound Ruleset by name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundRulesetRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Outbound Ruleset name.`,
Type: schema.TypeString,
Optional: true,
},
},
}
}
package outbound_ruleset
import (
"encoding/json"
"log"
"strings"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_outbound_ruleset_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
// getOutboundRulesetFromResourceData maps data from schema ResourceData object to a platformclientv2.Ruleset
func getOutboundRulesetFromResourceData(d *schema.ResourceData) platformclientv2.Ruleset {
name := d.Get("name").(string)
return platformclientv2.Ruleset{
Name: &name,
ContactList: util.BuildSdkDomainEntityRef(d, "contact_list_id"),
Queue: util.BuildSdkDomainEntityRef(d, "queue_id"),
Rules: buildDialerules(d.Get("rules").([]interface{})),
}
}
// buildDialerules maps a []interface{} into a Genesys Cloud *[]platformclientv2.Dialerrule
func buildDialerules(rules []interface{}) *[]platformclientv2.Dialerrule {
rulesSlice := make([]platformclientv2.Dialerrule, 0)
for _, rule := range rules {
var sdkRule platformclientv2.Dialerrule
ruleMap, ok := rule.(map[string]interface{})
if !ok {
continue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkRule.Name, ruleMap, "name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkRule.Category, ruleMap, "category")
sdkRule.Order = platformclientv2.Int(ruleMap["order"].(int))
resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkRule.Conditions, ruleMap, "conditions", buildConditions)
resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkRule.Actions, ruleMap, "actions", buildDialeractions)
rulesSlice = append(rulesSlice, sdkRule)
}
return &rulesSlice
}
// buildConditions maps a []interface{} into a Genesys Cloud *[]platformclientv2.Condition
func buildConditions(conditions []interface{}) *[]platformclientv2.Condition {
conditionSlice := make([]platformclientv2.Condition, 0)
for _, conditions := range conditions {
var sdkCondition platformclientv2.Condition
conditionMap, ok := conditions.(map[string]interface{})
if !ok {
continue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.VarType, conditionMap, "type")
sdkCondition.Inverted = platformclientv2.Bool(conditionMap["inverted"].(bool))
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.AttributeName, conditionMap, "attribute_name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.Value, conditionMap, "value")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.ValueType, conditionMap, "value_type")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.Operator, conditionMap, "operator")
resourcedata.BuildSDKStringArrayValueIfNotNil(&sdkCondition.Codes, conditionMap, "codes")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.Property, conditionMap, "property")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.PropertyType, conditionMap, "property_type")
sdkCondition.DataAction = &platformclientv2.Domainentityref{Id: platformclientv2.String(conditionMap["data_action_id"].(string))}
sdkCondition.DataNotFoundResolution = platformclientv2.Bool(conditionMap["data_not_found_resolution"].(bool))
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.ContactIdField, conditionMap, "contact_id_field")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.CallAnalysisResultField, conditionMap, "call_analysis_result_field")
resourcedata.BuildSDKStringValueIfNotNil(&sdkCondition.AgentWrapupField, conditionMap, "agent_wrapup_field")
resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkCondition.ContactColumnToDataActionFieldMappings, conditionMap, "contact_column_to_data_action_field_mappings", buildContactcolumntodataactionfieldmappings)
resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkCondition.Predicates, conditionMap, "predicates", buildDataactionconditionpredicates)
conditionSlice = append(conditionSlice, sdkCondition)
}
return &conditionSlice
}
// buildDialeractions maps a []interface{} into a Genesys Cloud *[]platformclientv2.Dialeraction
func buildDialeractions(actions []interface{}) *[]platformclientv2.Dialeraction {
actionsSlice := make([]platformclientv2.Dialeraction, 0)
for _, action := range actions {
var sdkAction platformclientv2.Dialeraction
actionMap, ok := action.(map[string]interface{})
if !ok {
continue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkAction.VarType, actionMap, "type")
resourcedata.BuildSDKStringValueIfNotNil(&sdkAction.ActionTypeName, actionMap, "action_type_name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkAction.UpdateOption, actionMap, "update_option")
resourcedata.BuildSDKStringMapValueIfNotNil(&sdkAction.Properties, actionMap, "properties")
sdkAction.DataAction = &platformclientv2.Domainentityref{Id: platformclientv2.String(actionMap["data_action_id"].(string))}
resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkAction.ContactColumnToDataActionFieldMappings, actionMap, "contact_column_to_data_action_field_mappings", buildContactcolumntodataactionfieldmappings)
resourcedata.BuildSDKStringValueIfNotNil(&sdkAction.ContactIdField, actionMap, "contact_id_field")
resourcedata.BuildSDKStringValueIfNotNil(&sdkAction.CallAnalysisResultField, actionMap, "call_analysis_result_field")
resourcedata.BuildSDKStringValueIfNotNil(&sdkAction.AgentWrapupField, actionMap, "agent_wrapup_field")
actionsSlice = append(actionsSlice, sdkAction)
}
return &actionsSlice
}
// buildContactcolumntodataactionfieldmappings maps a []interface{} into a Genesys Cloud *[]platformclientv2.Contactcolumntodataactionfieldmapping
func buildContactcolumntodataactionfieldmappings(fieldmappings []interface{}) *[]platformclientv2.Contactcolumntodataactionfieldmapping {
fieldmappingsSlice := make([]platformclientv2.Contactcolumntodataactionfieldmapping, 0)
for _, fieldmapping := range fieldmappings {
var sdkFieldmapping platformclientv2.Contactcolumntodataactionfieldmapping
fieldmappingMap, ok := fieldmapping.(map[string]interface{})
if !ok {
continue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkFieldmapping.ContactColumnName, fieldmappingMap, "contact_column_name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkFieldmapping.DataActionField, fieldmappingMap, "data_action_field")
fieldmappingsSlice = append(fieldmappingsSlice, sdkFieldmapping)
}
return &fieldmappingsSlice
}
// buildDataactionconditionpredicates maps a []interface{} into a Genesys Cloud *[]platformclientv2.Dataactionconditionpredicate
func buildDataactionconditionpredicates(predicates []interface{}) *[]platformclientv2.Dataactionconditionpredicate {
predicatesSlice := make([]platformclientv2.Dataactionconditionpredicate, 0)
for _, predicate := range predicates {
var sdkPredicate platformclientv2.Dataactionconditionpredicate
predicateMap, ok := predicate.(map[string]interface{})
if !ok {
continue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkPredicate.OutputField, predicateMap, "output_field")
resourcedata.BuildSDKStringValueIfNotNil(&sdkPredicate.OutputOperator, predicateMap, "output_operator")
resourcedata.BuildSDKStringValueIfNotNil(&sdkPredicate.ComparisonValue, predicateMap, "comparison_value")
sdkPredicate.Inverted = platformclientv2.Bool(predicateMap["inverted"].(bool))
sdkPredicate.OutputFieldMissingResolution = platformclientv2.Bool(predicateMap["output_field_missing_resolution"].(bool))
predicatesSlice = append(predicatesSlice, sdkPredicate)
}
return &predicatesSlice
}
// flattenDialerrules maps a Genesys Cloud *[]platformclientv2.Dialerrule into a []interface{}
func flattenDialerrules(rules *[]platformclientv2.Dialerrule) []interface{} {
if len(*rules) == 0 {
return nil
}
var dialerruleList []interface{}
for _, rule := range *rules {
ruleMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(ruleMap, "name", rule.Name)
resourcedata.SetMapValueIfNotNil(ruleMap, "order", rule.Order)
resourcedata.SetMapValueIfNotNil(ruleMap, "category", rule.Category)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(ruleMap, "conditions", rule.Conditions, flattenConditions)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(ruleMap, "actions", rule.Actions, flattenDialeractions)
dialerruleList = append(dialerruleList, ruleMap)
}
return dialerruleList
}
// flattenDialeractions maps a Genesys Cloud *[]platformclientv2.Dialeraction into a []interface{}
func flattenDialeractions(actions *[]platformclientv2.Dialeraction) []interface{} {
if len(*actions) == 0 {
return nil
}
var actionList []interface{}
for _, action := range *actions {
actionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(actionMap, "type", action.VarType)
resourcedata.SetMapValueIfNotNil(actionMap, "action_type_name", action.ActionTypeName)
resourcedata.SetMapValueIfNotNil(actionMap, "update_option", action.UpdateOption)
resourcedata.SetMapValueIfNotNil(actionMap, "contact_id_field", action.ContactIdField)
resourcedata.SetMapValueIfNotNil(actionMap, "call_analysis_result_field", action.CallAnalysisResultField)
resourcedata.SetMapValueIfNotNil(actionMap, "agent_wrapup_field", action.AgentWrapupField)
resourcedata.SetMapReferenceValueIfNotNil(actionMap, "data_action_id", action.DataAction)
resourcedata.SetMapStringMapValueIfNotNil(actionMap, "properties", action.Properties)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(actionMap, "contact_column_to_data_action_field_mappings", action.ContactColumnToDataActionFieldMappings, flattenContactcolumntodataactionfieldmappings)
actionList = append(actionList, actionMap)
}
return actionList
}
// flattenConditions maps a Genesys Cloud *[]platformclientv2.Condition into a []interface{}
func flattenConditions(conditions *[]platformclientv2.Condition) []interface{} {
if len(*conditions) == 0 {
return nil
}
var conditionList []interface{}
for _, condition := range *conditions {
conditionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(conditionMap, "type", condition.VarType)
resourcedata.SetMapValueIfNotNil(conditionMap, "inverted", condition.Inverted)
resourcedata.SetMapValueIfNotNil(conditionMap, "attribute_name", condition.AttributeName)
resourcedata.SetMapValueIfNotNil(conditionMap, "value", condition.Value)
resourcedata.SetMapValueIfNotNil(conditionMap, "value_type", condition.ValueType)
resourcedata.SetMapValueIfNotNil(conditionMap, "operator", condition.Operator)
resourcedata.SetMapStringArrayValueIfNotNil(conditionMap, "codes", condition.Codes)
resourcedata.SetMapValueIfNotNil(conditionMap, "property", condition.Property)
resourcedata.SetMapValueIfNotNil(conditionMap, "property_type", condition.PropertyType)
resourcedata.SetMapReferenceValueIfNotNil(conditionMap, "data_action_id", condition.DataAction)
resourcedata.SetMapValueIfNotNil(conditionMap, "data_not_found_resolution", condition.DataNotFoundResolution)
resourcedata.SetMapValueIfNotNil(conditionMap, "contact_id_field", condition.ContactIdField)
resourcedata.SetMapValueIfNotNil(conditionMap, "call_analysis_result_field", condition.CallAnalysisResultField)
resourcedata.SetMapValueIfNotNil(conditionMap, "agent_wrapup_field", condition.AgentWrapupField)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionMap, "contact_column_to_data_action_field_mappings", condition.ContactColumnToDataActionFieldMappings, flattenContactcolumntodataactionfieldmappings)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionMap, "predicates", condition.Predicates, flattenDataactionconditionpredicates)
conditionList = append(conditionList, conditionMap)
}
return conditionList
}
// flattenContactcolumntodataactionfieldmappings maps a Genesys Cloud *[]platformclientv2.Contactcolumntodataactionfieldmapping into a []interface{}
func flattenContactcolumntodataactionfieldmappings(fieldmappings *[]platformclientv2.Contactcolumntodataactionfieldmapping) []interface{} {
if len(*fieldmappings) == 0 {
return nil
}
var fieldmappingsList []interface{}
for _, fieldmapping := range *fieldmappings {
fieldmappingMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(fieldmappingMap, "contact_column_name", fieldmapping.ContactColumnName)
resourcedata.SetMapValueIfNotNil(fieldmappingMap, "data_action_field", fieldmapping.DataActionField)
fieldmappingsList = append(fieldmappingsList, fieldmappingMap)
}
return fieldmappingsList
}
// flattenDataactionconditionpredicates maps a Genesys Cloud *[]platformclientv2.Dataactionconditionpredicate into a []interface{}
func flattenDataactionconditionpredicates(predicates *[]platformclientv2.Dataactionconditionpredicate) []interface{} {
if len(*predicates) == 0 {
return nil
}
var predicateList []interface{}
for _, predicate := range *predicates {
predicateMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(predicateMap, "output_field", predicate.OutputField)
resourcedata.SetMapValueIfNotNil(predicateMap, "output_operator", predicate.OutputOperator)
resourcedata.SetMapValueIfNotNil(predicateMap, "comparison_value", predicate.ComparisonValue)
resourcedata.SetMapValueIfNotNil(predicateMap, "inverted", predicate.Inverted)
resourcedata.SetMapValueIfNotNil(predicateMap, "output_field_missing_resolution", predicate.OutputFieldMissingResolution)
predicateList = append(predicateList, predicateMap)
}
return predicateList
}
// look through rule actions to check if the referenced skills exist in our skill map or not
func doesRuleActionsRefDeletedSkill(rule platformclientv2.Dialerrule, skillMap resourceExporter.ResourceIDMetaMap) bool {
if rule.Actions == nil {
return false
}
for _, action := range *rule.Actions {
if action.ActionTypeName != nil && strings.EqualFold(*action.ActionTypeName, "set_skills") && action.Properties != nil {
if value, found := (*action.Properties)["skills"]; found {
// the property value is a json string wrapping an array of skill ids, need to convert it back to a slice to check if each skill exists
var skillIds []string
err := json.Unmarshal([]byte(value), &skillIds)
if err != nil {
log.Printf("doesRuleActionsRefDeletedSkill.Error unmarshaling skills JSON: %s", err)
return true
}
for _, skillId := range skillIds {
_, found := skillMap[skillId]
if !found { // skill id referenced by the rule action is not found in the skill map
log.Printf("The skill id '%s' used in action does not exist in GC anymore", skillId)
return true
}
}
}
}
}
return false
}
// look through rule conditions to check if the referenced skills exist in our skill map or not
func doesRuleConditionsRefDeletedSkill(rule platformclientv2.Dialerrule, skillMap resourceExporter.ResourceIDMetaMap) bool {
for _, condition := range *rule.Conditions {
if condition.AttributeName != nil && strings.EqualFold(*condition.AttributeName, "skill") && condition.Value != nil {
var found bool
for _, value := range skillMap {
if value.Name == *condition.Value {
found = true
break // found skill, evaluate next condition
}
}
if !found { // skill name referenced by rule condition is not found in the skill map
log.Printf("The skill name '%s' used in condition does not exist in GC anymore", *condition.Value)
return true
}
}
}
return false
}
package outbound_sequence
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_outbound_sequence.go contains the data source implementation
for the resource.
*/
// dataSourceOutboundSequenceRead retrieves by name the id in question
func dataSourceOutboundSequenceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newOutboundSequenceProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
campaignSequenceId, retryable, resp, err := proxy.getOutboundSequenceIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching outbound sequence %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No outbound sequence found with name %s", name), resp))
}
d.SetId(campaignSequenceId)
return nil
})
}
package outbound_sequence
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_outbound_sequence_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundSequenceProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createOutboundSequenceFunc func(ctx context.Context, p *outboundSequenceProxy, campaignSequence *platformclientv2.Campaignsequence) (*platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error)
type getAllOutboundSequenceFunc func(ctx context.Context, p *outboundSequenceProxy) (*[]platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error)
type getOutboundSequenceIdByNameFunc func(ctx context.Context, p *outboundSequenceProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getOutboundSequenceByIdFunc func(ctx context.Context, p *outboundSequenceProxy, id string) (campaignSequence *platformclientv2.Campaignsequence, response *platformclientv2.APIResponse, err error)
type updateOutboundSequenceFunc func(ctx context.Context, p *outboundSequenceProxy, id string, campaignSequence *platformclientv2.Campaignsequence) (*platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error)
type deleteOutboundSequenceFunc func(ctx context.Context, p *outboundSequenceProxy, id string) (response *platformclientv2.APIResponse, err error)
// outboundSequenceProxy contains all of the methods that call genesys cloud APIs.
type outboundSequenceProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
createOutboundSequenceAttr createOutboundSequenceFunc
getAllOutboundSequenceAttr getAllOutboundSequenceFunc
getOutboundSequenceIdByNameAttr getOutboundSequenceIdByNameFunc
getOutboundSequenceByIdAttr getOutboundSequenceByIdFunc
updateOutboundSequenceAttr updateOutboundSequenceFunc
deleteOutboundSequenceAttr deleteOutboundSequenceFunc
}
// newOutboundSequenceProxy initializes the outbound sequence proxy with all of the data needed to communicate with Genesys Cloud
func newOutboundSequenceProxy(clientConfig *platformclientv2.Configuration) *outboundSequenceProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundSequenceProxy{
clientConfig: clientConfig,
outboundApi: api,
createOutboundSequenceAttr: createOutboundSequenceFn,
getAllOutboundSequenceAttr: getAllOutboundSequenceFn,
getOutboundSequenceIdByNameAttr: getOutboundSequenceIdByNameFn,
getOutboundSequenceByIdAttr: getOutboundSequenceByIdFn,
updateOutboundSequenceAttr: updateOutboundSequenceFn,
deleteOutboundSequenceAttr: deleteOutboundSequenceFn,
}
}
// getOutboundSequenceProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundSequenceProxy(clientConfig *platformclientv2.Configuration) *outboundSequenceProxy {
if internalProxy == nil {
internalProxy = newOutboundSequenceProxy(clientConfig)
}
return internalProxy
}
// createOutboundSequence creates a Genesys Cloud outbound sequence
func (p *outboundSequenceProxy) createOutboundSequence(ctx context.Context, outboundSequence *platformclientv2.Campaignsequence) (*platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error) {
return p.createOutboundSequenceAttr(ctx, p, outboundSequence)
}
// getOutboundSequence retrieves all Genesys Cloud outbound sequence
func (p *outboundSequenceProxy) getAllOutboundSequence(ctx context.Context) (*[]platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error) {
return p.getAllOutboundSequenceAttr(ctx, p)
}
// getOutboundSequenceIdByName returns a single Genesys Cloud outbound sequence by a name
func (p *outboundSequenceProxy) getOutboundSequenceIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getOutboundSequenceIdByNameAttr(ctx, p, name)
}
// getOutboundSequenceById returns a single Genesys Cloud outbound sequence by Id
func (p *outboundSequenceProxy) getOutboundSequenceById(ctx context.Context, id string) (outboundSequence *platformclientv2.Campaignsequence, response *platformclientv2.APIResponse, err error) {
return p.getOutboundSequenceByIdAttr(ctx, p, id)
}
// updateOutboundSequence updates a Genesys Cloud outbound sequence
func (p *outboundSequenceProxy) updateOutboundSequence(ctx context.Context, id string, outboundSequence *platformclientv2.Campaignsequence) (*platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error) {
return p.updateOutboundSequenceAttr(ctx, p, id, outboundSequence)
}
// deleteOutboundSequence deletes a Genesys Cloud outbound sequence by Id
func (p *outboundSequenceProxy) deleteOutboundSequence(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteOutboundSequenceAttr(ctx, p, id)
}
// createOutboundSequenceFn is an implementation function for creating a Genesys Cloud outbound sequence
func createOutboundSequenceFn(ctx context.Context, p *outboundSequenceProxy, outboundSequence *platformclientv2.Campaignsequence) (*platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error) {
campaignSequence, resp, err := p.outboundApi.PostOutboundSequences(*outboundSequence)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create outbound sequence: %s", err)
}
return campaignSequence, resp, nil
}
// getAllOutboundSequenceFn is the implementation for retrieving all outbound sequence in Genesys Cloud
func getAllOutboundSequenceFn(ctx context.Context, p *outboundSequenceProxy) (*[]platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error) {
var allCampaignSequences []platformclientv2.Campaignsequence
const pageSize = 100
campaignSequences, resp, err := p.outboundApi.GetOutboundSequences(pageSize, 1, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get campaign sequence: %v", err)
}
if campaignSequences.Entities == nil || len(*campaignSequences.Entities) == 0 {
return &allCampaignSequences, resp, nil
}
for _, campaignSequence := range *campaignSequences.Entities {
allCampaignSequences = append(allCampaignSequences, campaignSequence)
}
for pageNum := 2; pageNum <= *campaignSequences.PageCount; pageNum++ {
campaignSequences, resp, err := p.outboundApi.GetOutboundSequences(pageSize, pageNum, true, "", "", "", "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get campaign sequence: %v", err)
}
if campaignSequences.Entities == nil || len(*campaignSequences.Entities) == 0 {
break
}
for _, campaignSequence := range *campaignSequences.Entities {
allCampaignSequences = append(allCampaignSequences, campaignSequence)
}
}
return &allCampaignSequences, resp, nil
}
// getOutboundSequenceIdByNameFn is an implementation of the function to get a Genesys Cloud outbound sequence by name
func getOutboundSequenceIdByNameFn(ctx context.Context, p *outboundSequenceProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
campaignSequences, resp, err := p.outboundApi.GetOutboundSequences(100, 1, true, "", name, "", "")
if err != nil {
return "", false, resp, err
}
if campaignSequences.Entities == nil || len(*campaignSequences.Entities) == 0 {
return "", true, resp, fmt.Errorf("No outbound sequence found with name %s", name)
}
for _, campaignSequence := range *campaignSequences.Entities {
if *campaignSequence.Name == name {
log.Printf("Retrieved the outbound sequence id %s by name %s", *campaignSequence.Id, name)
return *campaignSequence.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find outbound sequence with name %s", name)
}
// getOutboundSequenceByIdFn is an implementation of the function to get a Genesys Cloud outbound sequence by Id
func getOutboundSequenceByIdFn(ctx context.Context, p *outboundSequenceProxy, id string) (outboundSequence *platformclientv2.Campaignsequence, response *platformclientv2.APIResponse, err error) {
campaignSequence, resp, err := p.outboundApi.GetOutboundSequence(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve outbound sequence by id %s: %s", id, err)
}
return campaignSequence, resp, nil
}
// updateOutboundSequenceFn is an implementation of the function to update a Genesys Cloud outbound sequence
func updateOutboundSequenceFn(ctx context.Context, p *outboundSequenceProxy, id string, outboundSequence *platformclientv2.Campaignsequence) (*platformclientv2.Campaignsequence, *platformclientv2.APIResponse, error) {
sequence, resp, err := getOutboundSequenceByIdFn(ctx, p, id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to sequence by id %s: %s", id, err)
}
outboundSequence.Version = sequence.Version
campaignSequence, resp, err := p.outboundApi.PutOutboundSequence(id, *outboundSequence)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update outbound sequence: %s", err)
}
return campaignSequence, resp, nil
}
// deleteOutboundSequenceFn is an implementation function for deleting a Genesys Cloud outbound sequence
func deleteOutboundSequenceFn(ctx context.Context, p *outboundSequenceProxy, id string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.outboundApi.DeleteOutboundSequence(id)
if err != nil {
return resp, fmt.Errorf("Failed to delete outbound sequence: %s", err)
}
return resp, nil
}
package outbound_sequence
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_outbound_sequence.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthOutboundSequence retrieves all of the outbound sequence via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthOutboundSequences(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := newOutboundSequenceProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
campaignSequences, resp, err := proxy.getAllOutboundSequence(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get outbound sequences error: %s", err), resp)
}
for _, campaignSequence := range *campaignSequences {
resources[*campaignSequence.Id] = &resourceExporter.ResourceMeta{Name: *campaignSequence.Name}
}
return resources, nil
}
// createOutboundSequence is used by the outbound_sequence resource to create Genesys cloud outbound sequence
func createOutboundSequence(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundSequenceProxy(sdkConfig)
status := d.Get("status").(string)
outboundSequence := getOutboundSequenceFromResourceData(d)
log.Printf("Creating outbound sequence %s", *outboundSequence.Name)
campaignSequence, resp, err := proxy.createOutboundSequence(ctx, &outboundSequence)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create outbound sequence %s error: %s", *outboundSequence.Name, err), resp)
}
d.SetId(*campaignSequence.Id)
// Campaigns sequences can be enabled after creation
if status == "on" {
d.Set("status", status)
diag := updateOutboundSequence(ctx, d, meta)
if diag != nil {
return diag
}
}
log.Printf("Created outbound sequence %s", *campaignSequence.Id)
return readOutboundSequence(ctx, d, meta)
}
// readOutboundSequence is used by the outbound_sequence resource to read an outbound sequence from genesys cloud
func readOutboundSequence(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundSequenceProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundSequence(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading outbound sequence %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
campaignSequence, resp, getErr := proxy.getOutboundSequenceById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read outbound sequence %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read outbound sequence %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", campaignSequence.Name)
if campaignSequence.Campaigns != nil {
d.Set("campaign_ids", util.SdkDomainEntityRefArrToList(*campaignSequence.Campaigns))
}
resourcedata.SetNillableValue(d, "status", campaignSequence.Status)
resourcedata.SetNillableValue(d, "repeat", campaignSequence.Repeat)
log.Printf("Read outbound sequence %s %s", d.Id(), *campaignSequence.Name)
return cc.CheckState(d)
})
}
// updateOutboundSequence is used by the outbound_sequence resource to update an outbound sequence in Genesys Cloud
func updateOutboundSequence(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundSequenceProxy(sdkConfig)
status := d.Get("status").(string)
outboundSequence := getOutboundSequenceFromResourceData(d)
if status != "off" {
outboundSequence.Status = &status
}
log.Printf("Updating outbound sequence %s", *outboundSequence.Name)
campaignSequence, resp, err := proxy.updateOutboundSequence(ctx, d.Id(), &outboundSequence)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update outbound sequence %s error: %s", *outboundSequence.Name, err), resp)
}
log.Printf("Updated outbound sequence %s", *campaignSequence.Id)
return readOutboundSequence(ctx, d, meta)
}
// deleteOutboundSequence is used by the outbound_sequence resource to delete an outbound sequence from Genesys cloud
func deleteOutboundSequence(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundSequenceProxy(sdkConfig)
// Sequence can't be deleted while running
sequence, resp, err := proxy.getOutboundSequenceById(ctx, d.Id())
if *sequence.Status == "on" {
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get outbound sequence %s error: %s", d.Id(), err), resp)
}
sequence.Status = platformclientv2.String("off")
_, resp, err = proxy.updateOutboundSequence(ctx, d.Id(), sequence)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to turn off outbound sequence %s error: %s", d.Id(), err), resp)
}
time.Sleep(20 * time.Second) // Give the sequence a chance to turned off
}
resp, err = proxy.deleteOutboundSequence(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete outbound sequence %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getOutboundSequenceById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted outbound sequence %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting outbound sequence %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("outbound sequence %s still exists", d.Id()), resp))
})
}
package outbound_sequence
import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_outbound_sequence_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the outbound_sequence resource.
3. The datasource schema definitions for the outbound_sequence datasource.
4. The resource exporter configuration for the outbound_sequence exporter.
*/
const resourceName = "genesyscloud_outbound_sequence"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceOutboundSequence())
regInstance.RegisterDataSource(resourceName, DataSourceOutboundSequence())
regInstance.RegisterExporter(resourceName, OutboundSequenceExporter())
}
// ResourceOutboundSequence registers the genesyscloud_outbound_sequence resource with Terraform
func ResourceOutboundSequence() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud outbound sequence`,
CreateContext: provider.CreateWithPooledClient(createOutboundSequence),
ReadContext: provider.ReadWithPooledClient(readOutboundSequence),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundSequence),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundSequence),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `Name of outbound sequence`,
Required: true,
Type: schema.TypeString,
},
`campaign_ids`: {
Description: `The ordered list of Campaigns that this CampaignSequence will run.`,
Required: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`status`: {
Description: `The current status of the CampaignSequence. A CampaignSequence can be turned 'on' or 'off' (default). Changing from "on" to "off" will cause the current sequence to drop and be recreated with a new ID.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`on`, `off`}, false),
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return (old == `complete` && new == `on`)
},
},
`repeat`: {
Description: `Indicates if a sequence should repeat from the beginning after the last campaign completes. Default is false.`,
Optional: true,
Type: schema.TypeBool,
},
},
CustomizeDiff: customdiff.ForceNewIfChange("status", func(ctx context.Context, old, new, meta any) bool {
return new.(string) == "off" && (old.(string) == "on" || old.(string) == "complete")
}),
}
}
// OutboundSequenceExporter returns the resourceExporter object used to hold the genesyscloud_outbound_sequence exporter's config
func OutboundSequenceExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthOutboundSequences),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
`campaign_ids`: {
RefType: "genesyscloud_outbound_campaign",
},
},
}
}
// DataSourceOutboundSequence registers the genesyscloud_outbound_sequence data source
func DataSourceOutboundSequence() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud outbound sequence data source. Select an outbound sequence by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceOutboundSequenceRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Outbound Sequence name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package outbound_sequence
import (
"fmt"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_outbound_sequence.go contains all of the methods that perform the core logic for a resource.
*/
// getOutboundSequenceFromResourceData maps data from schema ResourceData object to a platformclientv2.Campaignsequence
func getOutboundSequenceFromResourceData(d *schema.ResourceData) platformclientv2.Campaignsequence {
return platformclientv2.Campaignsequence{
Name: platformclientv2.String(d.Get("name").(string)),
Campaigns: util.BuildSdkDomainEntityRefArr(d, "campaign_ids"),
Status: platformclientv2.String("off"), // This will be updated separately
Repeat: platformclientv2.Bool(d.Get("repeat").(bool)),
}
}
func GenerateOutboundSequence(
resourceId string,
name string,
campaignIds []string,
status string,
repeat string) string {
return fmt.Sprintf(`
resource "genesyscloud_outbound_sequence" "%s" {
name = "%s"
campaign_ids = [%s]
status = %s
repeat = %s
}
`, resourceId, name, strings.Join(campaignIds, ", "), status, repeat)
}
package outbound_settings
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_outbound_settings_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *outboundSettingsProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getOutboundSettingsByIdFunc func(ctx context.Context, p *outboundSettingsProxy, id string) (*platformclientv2.Outboundsettings, *platformclientv2.APIResponse, error)
type updateOutboundSettingsFunc func(ctx context.Context, p *outboundSettingsProxy, id string, outboundSettings *platformclientv2.Outboundsettings) (*platformclientv2.Outboundsettings, *platformclientv2.APIResponse, error)
// outboundSettingsProxy contains all of the methods that call genesys cloud APIs.
type outboundSettingsProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
getOutboundSettingsByIdAttr getOutboundSettingsByIdFunc
updateOutboundSettingsAttr updateOutboundSettingsFunc
}
// newOutboundSettingsProxy initializes the outbound settings proxy with all of the data needed to communicate with Genesys Cloud
func newOutboundSettingsProxy(clientConfig *platformclientv2.Configuration) *outboundSettingsProxy {
api := platformclientv2.NewOutboundApiWithConfig(clientConfig)
return &outboundSettingsProxy{
clientConfig: clientConfig,
outboundApi: api,
getOutboundSettingsByIdAttr: getOutboundSettingsByIdFn,
updateOutboundSettingsAttr: updateOutboundSettingsFn,
}
}
// getOutboundSettingsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getOutboundSettingsProxy(clientConfig *platformclientv2.Configuration) *outboundSettingsProxy {
if internalProxy == nil {
internalProxy = newOutboundSettingsProxy(clientConfig)
}
return internalProxy
}
// getOutboundSettingsById returns a single Genesys Cloud outbound settings by Id
func (p *outboundSettingsProxy) getOutboundSettingsById(ctx context.Context, id string) (*platformclientv2.Outboundsettings, *platformclientv2.APIResponse, error) {
return p.getOutboundSettingsByIdAttr(ctx, p, id)
}
// updateOutboundSettings updates a Genesys Cloud outbound settings
func (p *outboundSettingsProxy) updateOutboundSettings(ctx context.Context, id string, outboundSettings *platformclientv2.Outboundsettings) (*platformclientv2.Outboundsettings, *platformclientv2.APIResponse, error) {
return p.updateOutboundSettingsAttr(ctx, p, id, outboundSettings)
}
// getOutboundSettingsByIdFn is an implementation of the function to get a Genesys Cloud outbound settings by Id
func getOutboundSettingsByIdFn(ctx context.Context, p *outboundSettingsProxy, id string) (*platformclientv2.Outboundsettings, *platformclientv2.APIResponse, error) {
outboundSettings, resp, err := p.outboundApi.GetOutboundSettings()
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve outbound settings by id %s: %s", id, err)
}
return outboundSettings, resp, nil
}
// updateOutboundSettingsFn is an implementation of the function to update a Genesys Cloud outbound settings
func updateOutboundSettingsFn(ctx context.Context, p *outboundSettingsProxy, id string, outboundSettings *platformclientv2.Outboundsettings) (*platformclientv2.Outboundsettings, *platformclientv2.APIResponse, error) {
resp, err := p.outboundApi.PatchOutboundSettings(*outboundSettings)
if err != nil {
return nil, nil, fmt.Errorf("Failed to update outbound settings: %s", err)
}
return outboundSettings, resp, nil
}
package outbound_settings
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/tfexporter_state"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
/*
The resource_genesyscloud_outbound_settings.go contains all the methods that perform the core logic for a resource.
*/
func getAllOutboundSettings(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
resources["0"] = &resourceExporter.ResourceMeta{Name: "outbound_settings"}
return resources, nil
}
// createOutboundSettings is used by the outbound_settings resource to create Genesys cloud outbound settings
func createOutboundSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating Outbound Setting")
d.SetId("settings")
return updateOutboundSettings(ctx, d, meta)
}
// readOutboundSettings is used by the outbound_settings resource to read an outbound settings from genesys cloud
func readOutboundSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundSettingsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundSettings(), constants.DefaultConsistencyChecks, resourceName)
maxCallsPerAgent := d.Get("max_calls_per_agent").(int)
maxLineUtilization := d.Get("max_line_utilization").(float64)
abandonSeconds := d.Get("abandon_seconds").(float64)
complianceAbandonRateDenominator := d.Get("compliance_abandon_rate_denominator").(string)
automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").([]interface{})
rescheduleTimeZoneSkippedContacts := d.Get("reschedule_time_zone_skipped_contacts").(bool)
log.Printf("Reading Outbound setting %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
settings, resp, getErr := proxy.getOutboundSettingsById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Setting: %s", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Setting: %s", getErr), resp))
}
// Only read values if they are part of the terraform plan or during Export
if maxCallsPerAgent != 0 || tfexporter_state.IsExporterActive() {
resourcedata.SetNillableValue(d, "max_calls_per_agent", settings.MaxCallsPerAgent)
}
if maxLineUtilization != 0 || tfexporter_state.IsExporterActive() {
resourcedata.SetNillableValue(d, "max_line_utilization", settings.MaxLineUtilization)
}
if abandonSeconds != 0 || tfexporter_state.IsExporterActive() {
resourcedata.SetNillableValue(d, "abandon_seconds", settings.AbandonSeconds)
}
if complianceAbandonRateDenominator != "" || tfexporter_state.IsExporterActive() {
resourcedata.SetNillableValue(d, "compliance_abandon_rate_denominator", settings.ComplianceAbandonRateDenominator)
}
if settings.AutomaticTimeZoneMapping != nil && (len(automaticTimeZoneMapping) > 0 || tfexporter_state.IsExporterActive()) {
_ = d.Set("automatic_time_zone_mapping", flattenOutboundSettingsAutomaticTimeZoneMapping(*settings.AutomaticTimeZoneMapping, automaticTimeZoneMapping))
}
resourcedata.SetNillableValue(d, "reschedule_time_zone_skipped_contacts", &rescheduleTimeZoneSkippedContacts)
log.Printf("Read Outbound Setting")
return cc.CheckState(d)
})
}
// updateOutboundSettings is used by the outbound_settings resource to update an outbound settings in Genesys Cloud
func updateOutboundSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundSettingsProxy(sdkConfig)
maxCallsPerAgent := d.Get("max_calls_per_agent").(int)
maxLineUtilization := d.Get("max_line_utilization").(float64)
abandonSeconds := d.Get("abandon_seconds").(float64)
complianceAbandonRateDenominator := d.Get("compliance_abandon_rate_denominator").(string)
automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").([]interface{})
log.Printf("Updating Outbound Settings %s", d.Id())
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current Outbound settings version
setting, resp, getErr := proxy.getOutboundSettingsById(ctx, d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound Setting %s error: %s", d.Id(), getErr), resp)
}
update := platformclientv2.Outboundsettings{
Name: setting.Name,
Version: setting.Version,
RescheduleTimeZoneSkippedContacts: platformclientv2.Bool(d.Get("reschedule_time_zone_skipped_contacts").(bool)),
}
if maxCallsPerAgent != 0 || tfexporter_state.IsExporterActive() {
update.MaxCallsPerAgent = &maxCallsPerAgent
}
if maxLineUtilization != 0 || tfexporter_state.IsExporterActive() {
update.MaxLineUtilization = &maxLineUtilization
}
if abandonSeconds != 0 || tfexporter_state.IsExporterActive() {
update.AbandonSeconds = &abandonSeconds
}
if complianceAbandonRateDenominator != "" || tfexporter_state.IsExporterActive() {
update.ComplianceAbandonRateDenominator = &complianceAbandonRateDenominator
}
if automaticTimeZoneMapping != nil || tfexporter_state.IsExporterActive() {
update.AutomaticTimeZoneMapping = buildOutboundSettingsAutomaticTimeZoneMapping(d)
}
_, resp, err := proxy.updateOutboundSettings(ctx, d.Id(), &update)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound settings %s error: %s", *setting.Name, err), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Outbound settings %s", d.Id())
return readOutboundSettings(ctx, d, meta)
}
// deleteOutboundSettings is used by the outbound_settings resource to delete an outbound settings from Genesys cloud
func deleteOutboundSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}
package outbound_settings
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
/*
resource_genesycloud_outbound_settings_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the outbound_settings resource.
3. The datasource schema definitions for the outbound_settings datasource.
4. The resource exporter configuration for the outbound_settings exporter.
*/
const resourceName = "genesyscloud_outbound_settings"
// SetRegistrar registers all the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterResource(resourceName, ResourceOutboundSettings())
l.RegisterExporter(resourceName, OutboundSettingsExporter())
}
var (
automaticTimeZoneMappingResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`callable_windows`: {
Description: "The time intervals to use for automatic time zone mapping.",
Optional: true,
Type: schema.TypeSet,
MaxItems: 1,
Elem: callableWindowsResource,
},
`supported_countries`: {
Description: "The countries that are supported for automatic time zone mapping.",
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
callableWindowsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`mapped`: {
Description: "The time interval to place outbound calls, for contacts that can be mapped to a time zone.",
Optional: true,
Type: schema.TypeSet,
MaxItems: 1,
Elem: mappedResource,
},
`unmapped`: {
Description: "The time interval and time zone to place outbound calls, for contacts that cannot be mapped to a time zone.",
Optional: true,
Type: schema.TypeSet,
MaxItems: 1,
Elem: UnmappedResource,
},
},
}
mappedResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`earliest_callable_time`: {
Description: "The earliest time to dial a contact. Valid format is HH:mm",
Optional: true,
ValidateDiagFunc: gcloud.ValidateTimeHHMM,
Type: schema.TypeString,
},
`latest_callable_time`: {
Description: "The latest time to dial a contact. Valid format is HH:mm.",
Optional: true,
ValidateDiagFunc: gcloud.ValidateTimeHHMM,
Type: schema.TypeString,
},
},
}
UnmappedResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`earliest_callable_time`: {
Description: "The earliest time to dial a contact. Valid format is HH:mm.",
Optional: true,
ValidateDiagFunc: gcloud.ValidateTimeHHMM,
Type: schema.TypeString,
},
`latest_callable_time`: {
Description: "The latest time to dial a contact. Valid format is HH:mm.",
Optional: true,
ValidateDiagFunc: gcloud.ValidateTimeHHMM,
Type: schema.TypeString,
},
`time_zone_id`: {
Description: "The time zone to use for contacts that cannot be mapped.",
Optional: true,
Type: schema.TypeString,
},
},
}
)
// ResourceOutboundSettings registers the genesyscloud_outbound_settings resource with Terraform
func ResourceOutboundSettings() *schema.Resource {
return &schema.Resource{
Description: "An organization's outbound settings",
CreateContext: provider.CreateWithPooledClient(createOutboundSettings),
ReadContext: provider.ReadWithPooledClient(readOutboundSettings),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundSettings),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundSettings),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`max_calls_per_agent`: {
Description: "The maximum number of calls that can be placed per agent on any campaign.",
Optional: true,
Type: schema.TypeInt,
},
`max_line_utilization`: {
Description: "The maximum percentage of lines that should be used for Outbound, expressed as a decimal in the range [0.0, 1.0].",
Optional: true,
ValidateFunc: validation.FloatBetween(0.0, 1.0),
Type: schema.TypeFloat,
},
`abandon_seconds`: {
Description: "The number of seconds used to determine if a call is abandoned.",
Optional: true,
Type: schema.TypeFloat,
},
`compliance_abandon_rate_denominator`: {
Description: "The denominator to be used in determining the compliance abandon rate.Valid values: ALL_CALLS, CALLS_THAT_REACHED_QUEUE.",
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"ALL_CALLS", "CALLS_THAT_REACHED_QUEUE", ""}, false),
Type: schema.TypeString,
},
`automatic_time_zone_mapping`: {
Description: "The settings for automatic time zone mapping. Note that changing these settings will change them for both voice and messaging campaigns.",
Optional: true,
Type: schema.TypeList,
Elem: automaticTimeZoneMappingResource,
},
`reschedule_time_zone_skipped_contacts`: {
Description: "Whether or not to reschedule time-zone blocked contacts.",
Optional: true,
Type: schema.TypeBool,
},
},
}
}
func OutboundSettingsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundSettings),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
package outbound_settings
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/tfexporter_state"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
func buildOutboundSettingsAutomaticTimeZoneMapping(d *schema.ResourceData) *platformclientv2.Automatictimezonemappingsettings {
if mappingRequest := d.Get("automatic_time_zone_mapping"); mappingRequest != nil {
if mappingList := mappingRequest.([]interface{}); len(mappingList) > 0 {
mappingMap := mappingList[0].(map[string]interface{})
return &platformclientv2.Automatictimezonemappingsettings{
CallableWindows: buildCallableWindows(mappingMap["callable_windows"].(*schema.Set)),
SupportedCountries: buildSupportedCountries(d),
}
}
}
return nil
}
func buildSupportedCountries(d *schema.ResourceData) *[]string {
var supportedCountries []string
if countries, ok := d.GetOk("automatic_time_zone_mapping.0.supported_countries"); ok {
supportedCountries = lists.InterfaceListToStrings(countries.([]interface{}))
}
return &supportedCountries
}
func buildCallableWindows(windows *schema.Set) *[]platformclientv2.Callablewindow {
if windows == nil {
return nil
}
windowsSlice := make([]platformclientv2.Callablewindow, 0)
windowsList := windows.List()
for _, callableWindow := range windowsList {
var sdkCallableWindow platformclientv2.Callablewindow
callableWindowsMap := callableWindow.(map[string]interface{})
sdkCallableWindow.Mapped = buildCallableWindowsMapped(callableWindowsMap["mapped"].(*schema.Set))
sdkCallableWindow.Unmapped = buildCallableWindowsUnmapped(callableWindowsMap["unmapped"].(*schema.Set))
windowsSlice = append(windowsSlice, sdkCallableWindow)
}
return &windowsSlice
}
func buildCallableWindowsMapped(mappedWindows *schema.Set) *platformclientv2.Atzmtimeslot {
if mappedWindows != nil {
if mappedWindowsList := mappedWindows.List(); len(mappedWindowsList) > 0 {
mappedWindowsMap := mappedWindowsList[0].(map[string]interface{})
earliestCallableTime := mappedWindowsMap["earliest_callable_time"].(string)
latestCallableTime := mappedWindowsMap["latest_callable_time"].(string)
update := &platformclientv2.Atzmtimeslot{}
if earliestCallableTime != "" {
update.EarliestCallableTime = &earliestCallableTime
}
if latestCallableTime != "" {
update.LatestCallableTime = &latestCallableTime
}
return update
}
}
return &platformclientv2.Atzmtimeslot{}
}
func buildCallableWindowsUnmapped(unmappedWindows *schema.Set) *platformclientv2.Atzmtimeslotwithtimezone {
if unmappedWindows != nil {
if unmappedWindowsList := unmappedWindows.List(); len(unmappedWindowsList) > 0 {
unmappedWindowsMap := unmappedWindowsList[0].(map[string]interface{})
earliestCallableTime := unmappedWindowsMap["earliest_callable_time"].(string)
latestCallableTime := unmappedWindowsMap["latest_callable_time"].(string)
timeZoneId := unmappedWindowsMap["time_zone_id"].(string)
update := &platformclientv2.Atzmtimeslotwithtimezone{}
if earliestCallableTime != "" {
update.EarliestCallableTime = &earliestCallableTime
}
if latestCallableTime != "" {
update.LatestCallableTime = &latestCallableTime
}
if timeZoneId != "" {
update.TimeZoneId = &timeZoneId
}
return update
}
}
return &platformclientv2.Atzmtimeslotwithtimezone{}
}
func flattenOutboundSettingsAutomaticTimeZoneMapping(timeZoneMappings platformclientv2.Automatictimezonemappingsettings, automaticTimeZoneMapping []interface{}) []interface{} {
requestMap := make(map[string]interface{})
if tfexporter_state.IsExporterActive() {
if timeZoneMappings.CallableWindows != nil {
requestMap["callable_windows"] = flattenCallableWindows(*timeZoneMappings.CallableWindows, nil)
}
} else {
if len(automaticTimeZoneMapping) > 0 {
if callableWindows, ok := automaticTimeZoneMapping[0].(map[string]interface{})["callable_windows"].(*schema.Set); ok {
if timeZoneMappings.CallableWindows != nil {
requestMap["callable_windows"] = flattenCallableWindows(*timeZoneMappings.CallableWindows, callableWindows)
}
}
}
}
resourcedata.SetMapValueIfNotNil(requestMap, "supported_countries", timeZoneMappings.SupportedCountries)
return []interface{}{requestMap}
}
func flattenCallableWindows(windows []platformclientv2.Callablewindow, windowsSchema *schema.Set) *schema.Set {
if len(windows) == 0 {
return nil
}
callableWindowMap := make(map[string]interface{})
callableWindowsSet := schema.NewSet(schema.HashResource(callableWindowsResource), []interface{}{})
if tfexporter_state.IsExporterActive() {
for _, callableWindow := range windows {
if callableWindow.Mapped != nil {
callableWindowMap["mapped"] = flattenOutboundSettingsMapped(callableWindow.Mapped, nil)
}
if callableWindow.Unmapped != nil {
callableWindowMap["unmapped"] = flattenOutboundSettingsUnmapped(callableWindow.Unmapped, nil)
}
}
} else {
var mappedSchema *schema.Set
var unmappedSchema *schema.Set
for _, callableWindowsSchema := range windowsSchema.List() {
mappedSchema = callableWindowsSchema.(map[string]interface{})["mapped"].(*schema.Set)
unmappedSchema = callableWindowsSchema.(map[string]interface{})["unmapped"].(*schema.Set)
}
for _, callableWindow := range windows {
if callableWindow.Mapped != nil {
callableWindowMap["mapped"] = flattenOutboundSettingsMapped(callableWindow.Mapped, mappedSchema)
}
if callableWindow.Unmapped != nil {
callableWindowMap["unmapped"] = flattenOutboundSettingsUnmapped(callableWindow.Unmapped, unmappedSchema)
}
}
}
callableWindowsSet.Add(callableWindowMap)
return callableWindowsSet
}
func flattenOutboundSettingsMapped(mapped *platformclientv2.Atzmtimeslot, mappedSchema *schema.Set) *schema.Set {
requestSet := schema.NewSet(schema.HashResource(mappedResource), []interface{}{})
requestMap := make(map[string]interface{})
if tfexporter_state.IsExporterActive() {
resourcedata.SetMapValueIfNotNil(requestMap, "earliest_callable_time", mapped.EarliestCallableTime)
resourcedata.SetMapValueIfNotNil(requestMap, "latest_callable_time", mapped.LatestCallableTime)
} else {
mappedSchemaMap := mappedSchema.List()[0].(map[string]interface{})
earliestTimeSchema := mappedSchemaMap["earliest_callable_time"].(string)
latestTimeSchema := mappedSchemaMap["latest_callable_time"].(string)
if earliestTimeSchema != "" {
resourcedata.SetMapValueIfNotNil(requestMap, "earliest_callable_time", mapped.EarliestCallableTime)
}
if latestTimeSchema != "" {
resourcedata.SetMapValueIfNotNil(requestMap, "latest_callable_time", mapped.LatestCallableTime)
}
}
requestSet.Add(requestMap)
return requestSet
}
func flattenOutboundSettingsUnmapped(unmapped *platformclientv2.Atzmtimeslotwithtimezone, unmappedSchema *schema.Set) *schema.Set {
requestSet := schema.NewSet(schema.HashResource(UnmappedResource), []interface{}{})
requestMap := make(map[string]interface{})
if tfexporter_state.IsExporterActive() {
resourcedata.SetMapValueIfNotNil(requestMap, "earliest_callable_time", unmapped.EarliestCallableTime)
resourcedata.SetMapValueIfNotNil(requestMap, "latest_callable_time", unmapped.LatestCallableTime)
resourcedata.SetMapValueIfNotNil(requestMap, "time_zone_id", unmapped.TimeZoneId)
} else {
mappedSchemaMap := unmappedSchema.List()[0].(map[string]interface{})
earliestTimeSchema := mappedSchemaMap["earliest_callable_time"].(string)
latestTimeSchema := mappedSchemaMap["latest_callable_time"].(string)
timeZone := mappedSchemaMap["time_zone_id"].(string)
if earliestTimeSchema != "" {
resourcedata.SetMapValueIfNotNil(requestMap, "earliest_callable_time", unmapped.EarliestCallableTime)
}
if latestTimeSchema != "" {
resourcedata.SetMapValueIfNotNil(requestMap, "latest_callable_time", unmapped.LatestCallableTime)
}
if timeZone != "" {
resourcedata.SetMapValueIfNotNil(requestMap, "time_zone_id", unmapped.TimeZoneId)
}
}
requestSet.Add(requestMap)
return requestSet
}
package outbound_wrapupcode_mappings
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *outboundWrapupCodeMappingsProxy
type getAllOutboundWrapupCodeMappingsFunc func(ctx context.Context, p *outboundWrapupCodeMappingsProxy) (wrapupcodeMappings *platformclientv2.Wrapupcodemapping, resp *platformclientv2.APIResponse, err error)
type updateOutboundWrapUpCodeMappingsFunc func(ctx context.Context, p *outboundWrapupCodeMappingsProxy, outBoundWrappingCodes *platformclientv2.Wrapupcodemapping) (updatedWrapupCodeMappings *platformclientv2.Wrapupcodemapping, resp *platformclientv2.APIResponse, err error)
type getAllWrapupCodesFunc func(ctx context.Context, p *outboundWrapupCodeMappingsProxy) (updatedWrapupCodeMappings *[]platformclientv2.Wrapupcode, resp *platformclientv2.APIResponse, err error)
type outboundWrapupCodeMappingsProxy struct {
clientConfig *platformclientv2.Configuration
outboundApi *platformclientv2.OutboundApi
routingApi *platformclientv2.RoutingApi
getAllOutboundWrapupCodeMappingsAttr getAllOutboundWrapupCodeMappingsFunc
updateOutboundWrapUpCodeMappingsAttr updateOutboundWrapUpCodeMappingsFunc
getAllWrapupCodesAttr getAllWrapupCodesFunc
}
// newOutboundWrapupCodeMappingsProxy is a constructor to create a new outboundWrapupCodeMappingsProxy struct instance
func newOutboundWrapupCodeMappingsProxy(clientConfig *platformclientv2.Configuration) *outboundWrapupCodeMappingsProxy {
outboundApi := platformclientv2.NewOutboundApiWithConfig(clientConfig)
routingApi := platformclientv2.NewRoutingApiWithConfig(clientConfig)
return &outboundWrapupCodeMappingsProxy{
clientConfig: clientConfig,
outboundApi: outboundApi,
routingApi: routingApi,
getAllOutboundWrapupCodeMappingsAttr: getAllOutboundWrapupCodeMappingsFn,
updateOutboundWrapUpCodeMappingsAttr: updateOutboundWrapUpCodeMappingsFn,
getAllWrapupCodesAttr: getAllWrapupCodesFn,
}
}
// etOutboundWrapupCodeMappingsProxy is a singleton method to return a single instance outboundWrapupCodeMappingsProxy
func getOutboundWrapupCodeMappingsProxy(clientConfig *platformclientv2.Configuration) *outboundWrapupCodeMappingsProxy {
if internalProxy == nil {
internalProxy = newOutboundWrapupCodeMappingsProxy(clientConfig)
}
return internalProxy
}
// getAllOutboundWrapupCodeMapping returns all of the outbound mapping. This is the struct implementation that should be consumed by everypne.
func (p *outboundWrapupCodeMappingsProxy) getAllOutboundWrapupCodeMappings(ctx context.Context) (wrapupcodeMappings *platformclientv2.Wrapupcodemapping, resp *platformclientv2.APIResponse, err error) {
return p.getAllOutboundWrapupCodeMappingsAttr(ctx, p)
}
// updateOutboundWrapUpCodeMapping updates the outbound mappings. This is the struct implementation that should be consumed by everyone.
func (p *outboundWrapupCodeMappingsProxy) updateOutboundWrapUpCodeMappings(ctx context.Context, outBoundWrapupCodes platformclientv2.Wrapupcodemapping) (updatedWrapupCodeMappings *platformclientv2.Wrapupcodemapping, response *platformclientv2.APIResponse, err error) {
return p.updateOutboundWrapUpCodeMappingsAttr(ctx, p, &outBoundWrapupCodes)
}
// getAllWrapupCodes gets all the wrapup codes in the org. This is the struct implementation that should be consumed by everyone.
func (p *outboundWrapupCodeMappingsProxy) getAllWrapupCodes(ctx context.Context) (updatedWrapupCodeMappings *[]platformclientv2.Wrapupcode, resp *platformclientv2.APIResponse, err error) {
return p.getAllWrapupCodesAttr(ctx, p)
}
// getAllOutboundWrapupCodeMappingsFn( is the implementation of the getAllOutboundWrapupCodeMappings call
func getAllOutboundWrapupCodeMappingsFn(ctx context.Context, p *outboundWrapupCodeMappingsProxy) (wrapupcodeMappings *platformclientv2.Wrapupcodemapping, resp *platformclientv2.APIResponse, err error) {
wrapupcodemappings, resp, err := p.outboundApi.GetOutboundWrapupcodemappings()
return wrapupcodemappings, resp, err
}
// updateOutboundWrapUpCodeMappingsFn is the implementation of the updateOutboundWrapUpCodeMappings call
func updateOutboundWrapUpCodeMappingsFn(ctx context.Context, p *outboundWrapupCodeMappingsProxy, outBoundWrapupCodes *platformclientv2.Wrapupcodemapping) (updatedWrapupCodeMappings *platformclientv2.Wrapupcodemapping, resp *platformclientv2.APIResponse, err error) {
w, resp, err := p.outboundApi.PutOutboundWrapupcodemappings(*outBoundWrapupCodes)
if err != nil {
return nil, resp, fmt.Errorf("failed to update wrap-up code mappings: %s", err)
}
return w, resp, nil
}
// getAllWrapupCodesFn is the implementation of the getAllWrapupCodes call
func getAllWrapupCodesFn(ctx context.Context, p *outboundWrapupCodeMappingsProxy) (updatedWrapupCodeMappings *[]platformclientv2.Wrapupcode, response *platformclientv2.APIResponse, err error) {
wucs := []platformclientv2.Wrapupcode{}
const pageSize = 100
wucList, resp, err := p.routingApi.GetRoutingWrapupcodes(pageSize, 1, "", "", "", nil, nil)
if err != nil {
return nil, resp, err
}
wucs = append(wucs, *wucList.Entities...)
for pageNum := 2; pageNum <= *wucList.PageCount; pageNum++ {
wucList, resp, err := p.routingApi.GetRoutingWrapupcodes(pageSize, pageNum, "", "", "", nil, nil)
if err != nil {
return nil, resp, err
}
if wucList.Entities == nil || len(*wucList.Entities) == 0 {
break
}
wucs = append(wucs, *wucList.Entities...)
}
return &wucs, resp, nil
}
package outbound_wrapupcode_mappings
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const resourceName = "genesyscloud_outbound_wrapupcodemappings"
var (
flagsSchema = &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"CONTACT_UNCALLABLE", "NUMBER_UNCALLABLE", "RIGHT_PARTY_CONTACT"}, true),
}
mappingResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`wrapup_code_id`: {
Description: `The wrap-up code identifier.`,
Required: true,
Type: schema.TypeString,
},
`flags`: {
Description: `The set of wrap-up flags.`,
Required: true,
Type: schema.TypeSet,
Elem: flagsSchema,
},
},
}
)
// SetRegistrar registers the resource objects and the exporter. Note: There is no datasource implementation
func SetRegistrar(l registrar.Registrar) {
l.RegisterResource(resourceName, ResourceOutboundWrapUpCodeMappings())
l.RegisterExporter(resourceName, OutboundWrapupCodeMappingsExporter())
}
// OutboundWrapupCodeMappingsExporter() returns the exporter used for exporting the outbound wrapping codes
func OutboundWrapupCodeMappingsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getOutboundWrapupCodeMappings),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
`mappings.wrapup_code_id`: {
RefType: `genesyscloud_routing_wrapupcode`,
},
},
AllowEmptyArrays: []string{"default_set", "mappings.flags"},
}
}
// ResourceOutboundWrapUpCodeMappings returns the schema definition for outbound wrappings
func ResourceOutboundWrapUpCodeMappings() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Outbound Wrap-up Code Mappings`,
CreateContext: provider.CreateWithPooledClient(createOutboundWrapUpCodeMappings),
ReadContext: provider.ReadWithPooledClient(readOutboundWrapUpCodeMappings),
UpdateContext: provider.UpdateWithPooledClient(updateOutboundWrapUpCodeMappings),
DeleteContext: provider.DeleteWithPooledClient(deleteOutboundWrapUpCodeMappings),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`default_set`: {
Description: `The default set of wrap-up flags. These will be used if there is no entry for a given wrap-up code in the mapping.`,
Required: true,
Type: schema.TypeSet,
Elem: flagsSchema,
},
`mappings`: {
Description: `A map from wrap-up code identifiers to a set of wrap-up flags.`,
Optional: true,
Type: schema.TypeSet,
Elem: mappingResource,
},
`placeholder`: {
Description: `Placeholder data used internally by the provider.`,
Optional: true,
Type: schema.TypeString,
Default: "***",
ValidateFunc: validation.StringIsNotEmpty,
},
},
}
}
package outbound_wrapupcode_mappings
import (
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// flattenOutboundWrapupCodeMappings maps a Genesys Cloud Wrapupcodemapping to a schema.Set
// We allow preexisting WUC mappings to exist in Genesys Cloud so we filter by the defined WUC ids
// in the CX as Code configuration
//
// `wrapupCodeFilter` should contain the existing WUCs in the org, any mappings not in this list will be ignored.
// This is because deleted wrap up codes stil retain their mappings but there is no practical reason for processing
// them in CX as Code.
func flattenOutboundWrapupCodeMappings(d *schema.ResourceData, sdkWrapupcodemapping *platformclientv2.Wrapupcodemapping, wrapupCodeFilter *[]string) *schema.Set {
mappings := schema.NewSet(schema.HashResource(mappingResource), []interface{}{})
schemaMappings := d.Get("mappings").(*schema.Set)
schemaMappingsList := schemaMappings.List()
forExport := false
if _, ok := d.GetOk("placeholder"); !ok {
forExport = true
}
for sdkId, sdkFlags := range *sdkWrapupcodemapping.Mapping {
// If this is for export, we export all valid wuc mappings.
// ie no need to check if it's defined in the tf config file.
configuredMapping := false
if !forExport {
for _, sMap := range schemaMappingsList {
sMapI := sMap.(map[string]interface{})
if sMapI["wrapup_code_id"] == sdkId {
configuredMapping = true
break
}
}
}
if (!forExport && !configuredMapping) || !lists.ItemInSlice(sdkId, *wrapupCodeFilter) {
continue
}
setSdkFlags := schema.NewSet(schema.HashSchema(flagsSchema), []interface{}{})
for _, f := range sdkFlags {
setSdkFlags.Add(f)
}
currentMap := make(map[string]interface{}, 0)
currentMap["wrapup_code_id"] = sdkId
currentMap["flags"] = setSdkFlags
mappings.Add(currentMap)
}
return mappings
}
// buildWrapupCodeMappings builds the list of wrapupcode mappings from the schema object
func buildWrapupCodeMappings(d *schema.ResourceData) *map[string][]string {
wrapupCodeMappings := make(map[string][]string, 0)
if mappings := d.Get("mappings").(*schema.Set); mappings.Len() > 0 {
for _, m := range mappings.List() {
if mapping, ok := m.(map[string]interface{}); ok {
id := mapping["wrapup_code_id"].(string)
flags := lists.InterfaceListToStrings(mapping["flags"].(*schema.Set).List())
wrapupCodeMappings[id] = flags
}
}
}
return &wrapupCodeMappings
}
package outbound_wrapupcode_mappings
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// getOutboundWrapupCodeMappings is used by the exporter to return all wrapupcode mappings
func getOutboundWrapupCodeMappings(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
resources["0"] = &resourceExporter.ResourceMeta{Name: "wrapupcodemappings"}
return resources, nil
}
// createOutboundWrapUpCodeMappings is used to create the Terraform backing state associated with an outbound wrapup code mapping
func createOutboundWrapUpCodeMappings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating Outbound Wrap-up Code Mappings")
d.SetId("wrapupcodemappings")
return updateOutboundWrapUpCodeMappings(ctx, d, meta)
}
// readOutboundWrapUpCodeMappings reads the current state of the outboundwrapupcode mapping object
func readOutboundWrapUpCodeMappings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundWrapupCodeMappingsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundWrapUpCodeMappings(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Outbound Wrap-up Code Mappings")
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkWrapupCodeMappings, resp, err := proxy.getAllOutboundWrapupCodeMappings(ctx)
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Wrap-up Code Mappings: %s", err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Wrap-up Code Mappings: %s", err), resp))
}
wrapupCodes, resp, err := proxy.getAllWrapupCodes(ctx)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get wrapup codes: %s", err), resp))
}
resourcedata.SetNillableValue(d, "default_set", sdkWrapupCodeMappings.DefaultSet)
existingWrapupCodes := make([]string, 0)
for _, wuc := range *wrapupCodes {
existingWrapupCodes = append(existingWrapupCodes, *wuc.Id)
}
if sdkWrapupCodeMappings.Mapping != nil {
_ = d.Set("mappings", flattenOutboundWrapupCodeMappings(d, sdkWrapupCodeMappings, &existingWrapupCodes))
}
log.Print("Read Outbound Wrap-up Code Mappings")
return cc.CheckState(d)
})
}
// updateOutboundWrapUpCodeMappings is sued to update the Terraform backing state associated with an outbound wrapup code mapping
func updateOutboundWrapUpCodeMappings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getOutboundWrapupCodeMappingsProxy(sdkConfig)
log.Printf("Updating Outbound Wrap-up Code Mappings")
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
wrapupCodeMappings, resp, err := proxy.getAllOutboundWrapupCodeMappings(ctx)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get wrap-up code mappings error: %s", err), resp)
}
wrapupCodeUpdate := platformclientv2.Wrapupcodemapping{
DefaultSet: lists.BuildSdkStringList(d, "default_set"),
Mapping: buildWrapupCodeMappings(d),
Version: wrapupCodeMappings.Version,
}
_, resp, err = proxy.updateOutboundWrapUpCodeMappings(ctx, wrapupCodeUpdate)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update wrap-up code mappings %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Print("Updated Outbound Wrap-up Code Mappings")
return readOutboundWrapUpCodeMappings(ctx, d, meta)
}
// deleteOutboundWrapUpCodeMappings This a no up to satisfy the deletion of outbound wrapping resource
func deleteOutboundWrapUpCodeMappings(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
// Does not delete the wrap-up code mappings. This resource will just no longer manage them.
return nil
}
package process_automation_trigger
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type ProcessAutomationTriggers struct {
Entities *[]ProcessAutomationTrigger `json:"entities,omitempty"`
NextUri *string `json:"nextUri,omitempty"`
}
func dataSourceProcessAutomationTrigger() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud process automation trigger. Select a trigger by name",
ReadContext: provider.ReadWithPooledClient(dataSourceProcessAutomationTriggerRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the trigger",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceProcessAutomationTriggerRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
integrationAPI := platformclientv2.NewIntegrationsApiWithConfig(sdkConfig)
triggerName := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
// create path
path := integrationAPI.Configuration.BasePath + "/api/v2/processAutomation/triggers"
for pageNum := 1; ; pageNum++ {
processAutomationTriggers, resp, getErr := getAllProcessAutomationTriggers(path, integrationAPI)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get page of process automation triggers: %s", getErr), resp))
}
if processAutomationTriggers.Entities == nil || len(*processAutomationTriggers.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no process automation triggers found with name: %s", triggerName), resp))
}
for _, trigger := range *processAutomationTriggers.Entities {
if trigger.Name != nil && *trigger.Name == triggerName {
d.SetId(*trigger.Id)
return nil
}
}
if processAutomationTriggers.NextUri == nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no process automation triggers found with name: %s", getErr), resp))
}
path = integrationAPI.Configuration.BasePath + *processAutomationTriggers.NextUri
}
})
}
func getAllProcessAutomationTriggers(path string, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTriggers, *platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
headerParams := make(map[string]string)
// oauth required
if api.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
}
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *ProcessAutomationTriggers
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, nil, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
}
return successPayload, response, err
}
package process_automation_trigger
import (
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource("genesyscloud_processautomation_trigger", ResourceProcessAutomationTrigger())
regInstance.RegisterDataSource("genesyscloud_processautomation_trigger", dataSourceProcessAutomationTrigger())
regInstance.RegisterExporter("genesyscloud_processautomation_trigger", ProcessAutomationTriggerExporter())
}
package process_automation_trigger
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func postProcessAutomationTrigger(pat *ProcessAutomationTrigger, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTrigger, *platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
jsonStr, err := pat.toJSONString()
if err != nil {
return nil, nil, err
}
var jsonMap map[string]interface{}
json.Unmarshal([]byte(jsonStr), &jsonMap)
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers"
// add default headers if any
headerParams := make(map[string]string)
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *ProcessAutomationTrigger
response, err := apiClient.CallAPI(path, http.MethodPost, jsonMap, headerParams, nil, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
log.Printf("Process automation trigger created with Id %s and correlationId: %s", *successPayload.Id, response.CorrelationID)
}
return successPayload, response, err
}
func getProcessAutomationTrigger(triggerId string, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTrigger, *platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers/" + triggerId
headerParams := make(map[string]string)
// oauth required
if api.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
}
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, nil, nil, "", nil)
if response.Error != nil {
err = errors.New(response.ErrorMessage)
return nil, nil, err
}
successPayload, err := NewProcessAutomationFromPayload(response)
if err != nil {
return nil, response, err
}
return successPayload, response, err
}
func putProcessAutomationTrigger(triggerId string, pat *ProcessAutomationTrigger, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTrigger, *platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
jsonStr, err := pat.toJSONString()
if err != nil {
return nil, nil, err
}
var jsonMap map[string]interface{}
json.Unmarshal([]byte(jsonStr), &jsonMap)
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers/" + triggerId
headerParams := make(map[string]string)
// oauth required
if api.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
}
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *ProcessAutomationTrigger
response, err := apiClient.CallAPI(path, http.MethodPut, jsonMap, headerParams, nil, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
log.Printf("Process automation trigger updated with Id %s and correlationId: %s", *successPayload.Id, response.CorrelationID)
}
return successPayload, response, err
}
func deleteProcessAutomationTrigger(triggerId string, api *platformclientv2.IntegrationsApi) (*platformclientv2.APIResponse, error) {
apiClient := &api.Configuration.APIClient
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers/" + triggerId
headerParams := make(map[string]string)
// oauth required
if api.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
}
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
response, err := apiClient.CallAPI(path, http.MethodDelete, nil, headerParams, nil, nil, "", nil)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
}
return response, err
}
func getAllProcessAutomationTriggersResourceMap(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
integAPI := platformclientv2.NewIntegrationsApiWithConfig(clientConfig)
// create path and map variables
path := integAPI.Configuration.BasePath + "/api/v2/processAutomation/triggers"
for pageNum := 1; ; pageNum++ {
processAutomationTriggers, resp, getErr := getAllProcessAutomationTriggers(path, integAPI)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to get page of process automation triggers: %v", getErr), resp)
}
if processAutomationTriggers.Entities == nil || len(*processAutomationTriggers.Entities) == 0 {
break
}
for _, trigger := range *processAutomationTriggers.Entities {
resources[*trigger.Id] = &resourceExporter.ResourceMeta{Name: *trigger.Name}
}
if processAutomationTriggers.NextUri == nil {
break
}
path = integAPI.Configuration.BasePath + *processAutomationTriggers.NextUri
}
return resources, nil
}
package process_automation_trigger
import (
"encoding/json"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type ProcessAutomationTrigger struct {
Id *string `json:"id,omitempty"`
TopicName *string `json:"topicName,omitempty"`
Name *string `json:"name,omitempty"`
Target *Target `json:"target,omitempty"`
MatchCriteria *string `json:"-"`
Enabled *bool `json:"enabled,omitempty"`
EventTTLSeconds *int `json:"eventTTLSeconds,omitempty"`
DelayBySeconds *int `json:"delayBySeconds,omitempty"`
Version *int `json:"version,omitempty"`
Description *string `json:"description,omitempty"`
}
type WorkflowTargetSettings struct {
DataFormat *string `json:"dataFormat,omitempty"`
}
type Target struct {
Type *string `json:"type,omitempty"`
Id *string `json:"id,omitempty"`
WorkflowTargetSettings *WorkflowTargetSettings `json:"workflowTargetSettings,omitempty"`
}
func (p *ProcessAutomationTrigger) toJSONString() (string, error) {
//Step #1: Converting the process automation trigger to a JSON byte arrays
b, err := json.Marshal(p)
if err != nil {
return "", err
}
patJson := string(b)
//Step #2: Converting the JSON string to a Golang Map
var patMap map[string]interface{}
err = json.Unmarshal([]byte(patJson), &patMap)
if err != nil {
return "", err
}
//Step #3: Converting the MatchCriteria field from a string to Map
var data []map[string]interface{}
err = json.Unmarshal([]byte(*p.MatchCriteria), &data)
if err != nil {
return "", err
}
matchCriteriaArray := make([]interface{}, len(data))
for i, obj := range data {
value := make(map[string]interface{})
value["jsonPath"] = obj["jsonPath"]
value["operator"] = obj["operator"]
value["value"] = obj["value"]
value["values"] = obj["values"]
matchCriteriaArray[i] = value
}
//Step #4: Merging the match criteria array into the main map
patMap["matchCriteria"] = matchCriteriaArray
//Step #5: Converting the merged Map into a JSON string
finalJsonBytes, err := json.Marshal(patMap)
if err != nil {
return "", err
}
finalPAT := string(finalJsonBytes)
return finalPAT, nil
}
// Constructor that will take an platform client response object and build a new ProcessAutomationTrigger from it
func NewProcessAutomationFromPayload(response *platformclientv2.APIResponse) (*ProcessAutomationTrigger, error) {
httpPayload := response.RawBody
pat := &ProcessAutomationTrigger{}
patMap := make(map[string]interface{})
err := json.Unmarshal(httpPayload, &patMap)
if err != nil {
return nil, err
}
matchCriteria := patMap["matchCriteria"]
matchCriteriaBytes, err := json.Marshal(matchCriteria)
matchCriteriaStr := string(matchCriteriaBytes)
if err != nil {
return nil, err
}
err = json.Unmarshal(httpPayload, &pat)
if err != nil {
return nil, err
}
pat.MatchCriteria = &matchCriteriaStr
return pat, nil
}
package process_automation_trigger
import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"fmt"
"log"
"time"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
const (
resourceName = "genesyscloud_processautomation_trigger"
)
var (
workflowTargetSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"data_format": {
Description: "The data format to use when invoking target.",
Type: schema.TypeString,
Required: false,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"Json",
"TopLevelPrimitives",
}, false),
},
},
}
target = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "Type of the target the trigger is configured to hit",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"Workflow",
}, false),
},
"id": {
Description: "Id of the target the trigger is configured to hit",
Type: schema.TypeString,
Required: true,
},
"workflow_target_settings": {
Description: "Optional config for the target. Until the feature gets enabled will always operate in TopLevelPrimitives mode.",
Type: schema.TypeSet,
Required: false,
Optional: true,
MaxItems: 1,
Elem: workflowTargetSettings,
},
},
}
)
/*
NOTE:
This resource currently does not use the Go SDk and instead makes API calls directly.
The Go SDK can not properly handle process automation triggers due the value and values
attributes in the matchCriteria object being listed as JsonNode in the swagger docs.
A JsonNode is a placeholder type with no nested values which creates problems in Go
because it can't properly determine a type for the value/values field.
*/
func ResourceProcessAutomationTrigger() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Process Automation Trigger`,
CreateContext: provider.CreateWithPooledClient(createProcessAutomationTrigger),
ReadContext: provider.ReadWithPooledClient(readProcessAutomationTrigger),
UpdateContext: provider.UpdateWithPooledClient(updateProcessAutomationTrigger),
DeleteContext: provider.DeleteWithPooledClient(removeProcessAutomationTrigger),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the Trigger",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 256),
},
"topic_name": {
Description: "Topic name that will fire trigger. Changing the topic_name attribute will cause the processautomation_trigger object to be dropped and recreated with a new ID. ",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 256),
},
"enabled": {
Description: "Whether or not the trigger should be fired on events",
Type: schema.TypeBool,
Required: true,
},
"target": {
Description: "Target the trigger will invoke when fired",
Type: schema.TypeSet,
Optional: false,
Required: true,
MaxItems: 1,
Elem: target,
},
"match_criteria": {
Description: "Match criteria that controls when the trigger will fire. NOTE: The match_criteria field type has changed from a complex object to a string. This was done to allow for complex JSON object definitions.",
Type: schema.TypeString,
Optional: true,
},
"event_ttl_seconds": {
Description: "How old an event can be to fire the trigger. Must be an number greater than or equal to 10. Only one of event_ttl_seconds or delay_by_seconds can be set.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(10),
},
"delay_by_seconds": {
Description: "How long to delay processing of a trigger after an event passes the match criteria. Must be an number between 60 and 900 inclusive. Only one of event_ttl_seconds or delay_by_seconds can be set.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(60, 900),
},
"description": {
Description: "A description of the trigger",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 512),
},
},
}
}
func ProcessAutomationTriggerExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllProcessAutomationTriggersResourceMap),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"target.id": {RefType: "genesyscloud_flow"},
},
}
}
func createProcessAutomationTrigger(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
topic_name := d.Get("topic_name").(string)
enabled := d.Get("enabled").(bool)
eventTTLSeconds := d.Get("event_ttl_seconds").(int)
delayBySeconds := d.Get("delay_by_seconds").(int)
description := d.Get("description").(string)
matchingCriteria := d.Get("match_criteria").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
integAPI := platformclientv2.NewIntegrationsApiWithConfig(sdkConfig)
if eventTTLSeconds > 0 && delayBySeconds > 0 {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Only one of event_ttl_seconds or delay_by_seconds can be set."), fmt.Errorf("event_ttl_seconds and delay_by_seconds are both set"))
}
log.Printf("Creating process automation trigger %s", name)
triggerInput := &ProcessAutomationTrigger{
TopicName: &topic_name,
Name: &name,
Target: buildTarget(d),
MatchCriteria: &matchingCriteria,
Enabled: &enabled,
Description: &description,
}
if eventTTLSeconds > 0 {
triggerInput.EventTTLSeconds = &eventTTLSeconds
}
if delayBySeconds > 0 {
triggerInput.DelayBySeconds = &delayBySeconds
}
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
trigger, resp, err := postProcessAutomationTrigger(triggerInput, integAPI)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create process automation trigger %s error: %s", name, err), resp)
}
d.SetId(*trigger.Id)
log.Printf("Created process automation trigger %s %s", name, *trigger.Id)
return resp, nil
})
if diagErr != nil {
return diagErr
}
return readProcessAutomationTrigger(ctx, d, meta)
}
func readProcessAutomationTrigger(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
integAPI := platformclientv2.NewIntegrationsApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceProcessAutomationTrigger(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading process automation trigger %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
trigger, resp, getErr := getProcessAutomationTrigger(d.Id(), integAPI)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read process automation trigger %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to process read automation trigger %s | error: %s", d.Id(), getErr), resp))
}
if trigger.Name != nil {
d.Set("name", *trigger.Name)
} else {
d.Set("name", nil)
}
if trigger.TopicName != nil {
d.Set("topic_name", *trigger.TopicName)
} else {
d.Set("topic_name", nil)
}
d.Set("match_criteria", trigger.MatchCriteria)
d.Set("target", flattenTarget(trigger.Target))
if trigger.Enabled != nil {
d.Set("enabled", *trigger.Enabled)
} else {
d.Set("enabled", nil)
}
if trigger.EventTTLSeconds != nil {
d.Set("event_ttl_seconds", *trigger.EventTTLSeconds)
} else {
d.Set("event_ttl_seconds", nil)
}
if trigger.DelayBySeconds != nil {
d.Set("delay_by_seconds", *trigger.DelayBySeconds)
} else {
d.Set("delay_by_seconds", nil)
}
if trigger.Description != nil {
d.Set("description", *trigger.Description)
} else {
d.Set("description", nil)
}
log.Printf("Read process automation trigger %s %s", d.Id(), *trigger.Name)
return cc.CheckState(d)
})
}
func updateProcessAutomationTrigger(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
enabled := d.Get("enabled").(bool)
eventTTLSeconds := d.Get("event_ttl_seconds").(int)
delayBySeconds := d.Get("delay_by_seconds").(int)
description := d.Get("description").(string)
matchingCriteria := d.Get("match_criteria").(string)
topic_name := d.Get("topic_name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
integAPI := platformclientv2.NewIntegrationsApiWithConfig(sdkConfig)
log.Printf("Updating process automation trigger %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get the latest trigger version to send with PATCH
trigger, resp, getErr := getProcessAutomationTrigger(d.Id(), integAPI)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read process automation trigger %s error: %s", d.Id(), getErr), resp)
}
if eventTTLSeconds > 0 && delayBySeconds > 0 {
return resp, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Only one of event_ttl_seconds or delay_by_seconds can be set."), fmt.Errorf("event_ttl_seconds and delay_by_seconds are both set"))
}
triggerInput := &ProcessAutomationTrigger{
TopicName: &topic_name,
Name: &name,
Enabled: &enabled,
Target: buildTarget(d),
MatchCriteria: &matchingCriteria,
Version: trigger.Version,
Description: &description,
}
if eventTTLSeconds > 0 {
triggerInput.EventTTLSeconds = &eventTTLSeconds
}
if delayBySeconds > 0 {
triggerInput.DelayBySeconds = &delayBySeconds
}
_, putResp, err := putProcessAutomationTrigger(d.Id(), triggerInput, integAPI)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update process automation trigger %s error: %s", name, err), resp)
}
return putResp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated process automation trigger %s", name)
return readProcessAutomationTrigger(ctx, d, meta)
}
func removeProcessAutomationTrigger(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
integAPI := platformclientv2.NewIntegrationsApiWithConfig(sdkConfig)
log.Printf("Deleting process automation trigger %s", name)
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
resp, err := deleteProcessAutomationTrigger(d.Id(), integAPI)
if err != nil {
if util.IsStatus404(resp) {
log.Printf("process automation trigger already deleted %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("process automation trigger %s still exists", d.Id()), resp))
}
return nil
})
}
func buildTarget(d *schema.ResourceData) *Target {
if target := d.Get("target"); target != nil {
if targetList := target.(*schema.Set).List(); len(targetList) > 0 {
targetMap := targetList[0].(map[string]interface{})
targetType := targetMap["type"].(string)
id := targetMap["id"].(string)
target := &Target{
Type: &targetType,
Id: &id,
}
workflowTargetSettingsInput := targetMap["workflow_target_settings"].(*schema.Set).List()
if len(workflowTargetSettingsInput) > 0 {
workflowTargetSettingsInputMap := workflowTargetSettingsInput[0].(map[string]interface{})
dataFormat := workflowTargetSettingsInputMap["data_format"].(string)
if dataFormat == "" {
return target
}
target.WorkflowTargetSettings = &WorkflowTargetSettings{
DataFormat: &dataFormat,
}
}
return target
}
}
return &Target{}
}
func flattenTarget(inputTarget *Target) *schema.Set {
if inputTarget == nil {
return nil
}
targetSet := schema.NewSet(schema.HashResource(target), []interface{}{})
flattendedTarget := make(map[string]interface{})
flattendedTarget["id"] = *inputTarget.Id
flattendedTarget["type"] = *inputTarget.Type
if inputTarget.WorkflowTargetSettings != nil {
worklfowTargetSettingsSet := schema.NewSet(schema.HashResource(target), []interface{}{})
flattendedWorkflowTargetSettings := make(map[string]interface{})
flattendedWorkflowTargetSettings["data_format"] = *inputTarget.WorkflowTargetSettings.DataFormat
worklfowTargetSettingsSet.Add(flattendedWorkflowTargetSettings)
flattendedTarget["workflow_target_settings"] = worklfowTargetSettingsSet
}
targetSet.Add(flattendedTarget)
return targetSet
}
package provider
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"sync"
)
type JsonMap map[string]interface{}
// Attempt to get the home division once during a provider run
var divOnce sync.Once
var homeDivID string
var homeDivErr diag.Diagnostics
func getHomeDivisionID() (string, diag.Diagnostics) {
divOnce.Do(func() {
authAPI := platformclientv2.NewAuthorizationApi()
homeDiv, _, err := authAPI.GetAuthorizationDivisionsHome()
if err != nil {
homeDivErr = diag.Errorf("Failed to query home division: %s", err)
return
}
homeDivID = *homeDiv.Id
})
if homeDivErr != nil {
return "", homeDivErr
}
return homeDivID, nil
}
package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func init() {
// Set descriptions to support markdown syntax, this will be used in document generation
// and the language server.
// providerResources = make(map[string]*schema.Resource)
// providerDataSources = make(map[string]*schema.Resource)
schema.DescriptionKind = schema.StringMarkdown
// Customize the content of descriptions when output.
schema.SchemaDescriptionBuilder = func(s *schema.Schema) string {
desc := s.Description
if s.Default != nil {
desc += fmt.Sprintf(" Defaults to `%v`.", s.Default)
}
return strings.TrimSpace(desc)
}
}
// New initializes the provider schema
func New(version string, providerResources map[string]*schema.Resource, providerDataSources map[string]*schema.Resource) func() *schema.Provider {
return func() *schema.Provider {
/*
The next two lines are important. We have to make sure the Terraform provider has their own deep copies of the resource
and data source maps. If you do not do a deep copy and try to pass in the original maps, you open yourself up to race conditions
because they map are being read and written to concurrently.
*/
copiedResources := make(map[string]*schema.Resource)
for k, v := range providerResources {
copiedResources[k] = v
}
copiedDataSources := make(map[string]*schema.Resource)
for k, v := range providerDataSources {
copiedDataSources[k] = v
}
return &schema.Provider{
Schema: map[string]*schema.Schema{
"access_token": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_ACCESS_TOKEN", nil),
Description: "A string that the OAuth client uses to make requests. Can be set with the `GENESYSCLOUD_ACCESS_TOKEN` environment variable.",
},
"oauthclient_id": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_OAUTHCLIENT_ID", nil),
Description: "OAuthClient ID found on the OAuth page of Admin UI. Can be set with the `GENESYSCLOUD_OAUTHCLIENT_ID` environment variable.",
},
"oauthclient_secret": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_OAUTHCLIENT_SECRET", nil),
Description: "OAuthClient secret found on the OAuth page of Admin UI. Can be set with the `GENESYSCLOUD_OAUTHCLIENT_SECRET` environment variable.",
Sensitive: true,
},
"aws_region": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_REGION", nil),
Description: "AWS region where org exists. e.g. us-east-1. Can be set with the `GENESYSCLOUD_REGION` environment variable.",
ValidateFunc: validation.StringInSlice(getAllowedRegions(), true),
},
"sdk_debug": {
Type: schema.TypeBool,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_SDK_DEBUG", false),
Description: "Enables debug tracing in the Genesys Cloud SDK. Output will be written to the local file 'sdk_debug.log'.",
},
"sdk_debug_format": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_SDK_DEBUG_FORMAT", "Text"),
Description: "Specifies the data format of the 'sdk_debug.log'. Only applicable if sdk_debug is true. Default value is Text.",
ValidateFunc: validation.StringInSlice([]string{"Text", "Json"}, false),
},
"sdk_debug_file_path": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_SDK_DEBUG", "sdk_debug.log"),
Description: "Specifies the file path for the log file. Default value is sdk_debug.log",
ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile("^(|\\s+)$"), "Invalid File path "),
},
"token_pool_size": {
Type: schema.TypeInt,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_TOKEN_POOL_SIZE", 10),
Description: "Max number of OAuth tokens in the token pool. Can be set with the `GENESYSCLOUD_TOKEN_POOL_SIZE` environment variable.",
ValidateFunc: validation.IntBetween(1, 20),
},
"proxy": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"port": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_PROXY_PORT", nil),
Description: "Port for the proxy can be set with the `GENESYSCLOUD_PROXY_PORT` environment variable.",
},
"host": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_PROXY_HOST", nil),
Description: "Host for the proxy can be set with the `GENESYSCLOUD_PROXY_HOST` environment variable.",
},
"protocol": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_PROXY_PROTOCOL", nil),
Description: "Protocol for the proxy can be set with the `GENESYSCLOUD_PROXY_PROTOCOL` environment variable.",
},
"auth": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"username": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_PROXY_AUTH_USERNAME", nil),
Description: "UserName for the Auth can be set with the `GENESYSCLOUD_PROXY_AUTH_USERNAME` environment variable.",
},
"password": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_PROXY_AUTH_PASSWORD", nil),
Description: "Password for the Auth can be set with the `GENESYSCLOUD_PROXY_AUTH_PASSWORD` environment variable.",
},
},
},
},
},
},
},
},
ResourcesMap: copiedResources,
DataSourcesMap: copiedDataSources,
ConfigureContextFunc: configure(version),
}
}
}
type ProviderMeta struct {
Version string
ClientConfig *platformclientv2.Configuration
Domain string
}
func configure(version string) schema.ConfigureContextFunc {
return func(context context.Context, data *schema.ResourceData) (interface{}, diag.Diagnostics) {
// Initialize a single client if we have an access token
accessToken := data.Get("access_token").(string)
if accessToken != "" {
Once.Do(func() {
sdkConfig := platformclientv2.GetDefaultConfiguration()
_ = InitClientConfig(data, version, sdkConfig)
SdkClientPool = &SDKClientPool{
Pool: make(chan *platformclientv2.Configuration, 1),
}
SdkClientPool.Pool <- sdkConfig
})
} else {
// Initialize the SDK Client pool
err := InitSDKClientPool(data.Get("token_pool_size").(int), version, data)
if err != nil {
return nil, err
}
}
return &ProviderMeta{
Version: version,
ClientConfig: platformclientv2.GetDefaultConfiguration(),
Domain: getRegionDomain(data.Get("aws_region").(string)),
}, nil
}
}
func getRegionMap() map[string]string {
return map[string]string{
"dca": "inindca.com",
"tca": "inintca.com",
"us-east-1": "mypurecloud.com",
"us-east-2": "use2.us-gov-pure.cloud",
"us-west-2": "usw2.pure.cloud",
"eu-west-1": "mypurecloud.ie",
"eu-west-2": "euw2.pure.cloud",
"ap-southeast-2": "mypurecloud.com.au",
"ap-northeast-1": "mypurecloud.jp",
"eu-central-1": "mypurecloud.de",
"ca-central-1": "cac1.pure.cloud",
"ap-northeast-2": "apne2.pure.cloud",
"ap-south-1": "aps1.pure.cloud",
"sa-east-1": "sae1.pure.cloud",
"ap-northeast-3": "apne3.pure.cloud",
"eu-central-2": "euc2.pure.cloud",
"me-central-1": "mec1.pure.cloud",
}
}
func getAllowedRegions() []string {
regionMap := getRegionMap()
regionKeys := make([]string, 0, len(regionMap))
for k := range regionMap {
regionKeys = append(regionKeys, k)
}
return regionKeys
}
func getRegionDomain(region string) string {
return getRegionMap()[strings.ToLower(region)]
}
func GetRegionBasePath(region string) string {
return "https://api." + getRegionDomain(region)
}
func InitClientConfig(data *schema.ResourceData, version string, config *platformclientv2.Configuration) diag.Diagnostics {
accessToken := data.Get("access_token").(string)
oauthclientID := data.Get("oauthclient_id").(string)
oauthclientSecret := data.Get("oauthclient_secret").(string)
basePath := GetRegionBasePath(data.Get("aws_region").(string))
config.BasePath = basePath
diagErr := setUpSDKLogging(data, config)
if diagErr != nil {
return diagErr
}
setupProxy(data, config)
config.AddDefaultHeader("User-Agent", "GC Terraform Provider/"+version)
config.RetryConfiguration = &platformclientv2.RetryConfiguration{
RetryWaitMin: time.Second * 1,
RetryWaitMax: time.Second * 30,
RetryMax: 20,
RequestLogHook: func(request *http.Request, count int) {
if count > 0 && request != nil {
log.Printf("Retry #%d for %s %s", count, request.Method, request.URL)
}
},
ResponseLogHook: func(response *http.Response) {
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
log.Printf("Response %s for request:%s %s", response.Status, response.Request.Method, response.Request.URL)
}
},
}
if accessToken != "" {
log.Print("Setting access token set on configuration instance.")
config.AccessToken = accessToken
} else {
config.AutomaticTokenRefresh = true // Enable automatic token refreshing
return withRetries(context.Background(), time.Minute, func() *retry.RetryError {
err := config.AuthorizeClientCredentials(oauthclientID, oauthclientSecret)
if err != nil {
if !strings.Contains(err.Error(), "Auth Error: 400 - invalid_request (rate limit exceeded;") {
return retry.NonRetryableError(fmt.Errorf("failed to authorize Genesys Cloud client credentials: %v", err))
}
return retry.RetryableError(fmt.Errorf("exhausted retries on Genesys Cloud client credentials. %v", err))
}
return nil
})
}
log.Printf("Initialized Go SDK Client. Debug=%t", data.Get("sdk_debug").(bool))
return nil
}
func withRetries(ctx context.Context, timeout time.Duration, method func() *retry.RetryError) diag.Diagnostics {
err := diag.FromErr(retry.RetryContext(ctx, timeout, method))
if err != nil && strings.Contains(fmt.Sprintf("%v", err), "timeout while waiting for state to become") {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return withRetries(ctx, timeout, method)
}
return err
}
func setUpSDKLogging(data *schema.ResourceData, config *platformclientv2.Configuration) diag.Diagnostics {
sdkDebugFilePath := data.Get("sdk_debug_file_path").(string)
if data.Get("sdk_debug").(bool) {
config.LoggingConfiguration = &platformclientv2.LoggingConfiguration{
LogLevel: platformclientv2.LTrace,
LogRequestBody: true,
LogResponseBody: true,
}
config.LoggingConfiguration.SetLogToConsole(false)
config.LoggingConfiguration.SetLogFilePath(sdkDebugFilePath)
dir, _ := filepath.Split(sdkDebugFilePath)
if err := os.MkdirAll(dir, os.ModePerm); os.IsExist(err) {
return diag.Errorf("error while creating filepath for %s: %s", sdkDebugFilePath, err)
}
if format := data.Get("sdk_debug_format"); format == "Json" {
config.LoggingConfiguration.SetLogFormat(platformclientv2.JSON)
} else {
config.LoggingConfiguration.SetLogFormat(platformclientv2.Text)
}
}
return nil
}
func setupProxy(data *schema.ResourceData, config *platformclientv2.Configuration) {
proxySet := data.Get("proxy").(*schema.Set)
for _, proxyObj := range proxySet.List() {
proxy := proxyObj.(map[string]interface{})
// Retrieve the values of the `host`, `port`, and `protocol` attributes
host := proxy["host"].(string)
port := proxy["port"].(string)
protocol := proxy["protocol"].(string)
config.ProxyConfiguration = &platformclientv2.ProxyConfiguration{}
config.ProxyConfiguration.Host = host
config.ProxyConfiguration.Port = port
config.ProxyConfiguration.Protocol = protocol
authSet := proxy["auth"].(*schema.Set)
authList := authSet.List()
for _, authElement := range authList {
auth := authElement.(map[string]interface{})
username := auth["username"].(string)
password := auth["password"].(string)
config.ProxyConfiguration.Auth = &platformclientv2.Auth{}
config.ProxyConfiguration.Auth.UserName = username
config.ProxyConfiguration.Auth.Password = password
}
}
}
func AuthorizeSdk() (*platformclientv2.Configuration, error) {
// Create new config
sdkConfig := platformclientv2.GetDefaultConfiguration()
v, exists := os.LookupEnv("TF_UNIT")
if exists && v != "" {
log.Printf("TF_UNIT environment is set. No authorization of the SDK has occurred")
return sdkConfig, nil
}
sdkConfig.BasePath = GetRegionBasePath(os.Getenv("GENESYSCLOUD_REGION"))
diagErr := withRetries(context.Background(), time.Minute, func() *retry.RetryError {
err := sdkConfig.AuthorizeClientCredentials(os.Getenv("GENESYSCLOUD_OAUTHCLIENT_ID"), os.Getenv("GENESYSCLOUD_OAUTHCLIENT_SECRET"))
if err != nil {
if !strings.Contains(err.Error(), "Auth Error: 400 - invalid_request (rate limit exceeded;") {
return retry.NonRetryableError(fmt.Errorf("failed to authorize Genesys Cloud client credentials: %v", err))
}
return retry.RetryableError(fmt.Errorf("exhausted retries on Genesys Cloud client credentials. %v", err))
}
return nil
})
if diagErr != nil {
return sdkConfig, fmt.Errorf("%v", diagErr)
}
return sdkConfig, nil
}
package provider
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
// ProviderFactories are used to instantiate a provider during acceptance testing.
// The factory function will be invoked for every Terraform CLI command executed
// to create a provider server to which the CLI can reattach.
func GetProviderFactories(providerResources map[string]*schema.Resource, providerDataSources map[string]*schema.Resource) map[string]func() (*schema.Provider, error) {
return map[string]func() (*schema.Provider, error){
"genesyscloud": func() (*schema.Provider, error) {
provider := New("0.1.0", providerResources, providerDataSources)()
return provider, nil
},
}
}
// Verify default division is home division
func TestDefaultHomeDivision(resource string) resource.TestCheckFunc {
return func(state *terraform.State) error {
homeDivID, err := getHomeDivisionID()
if err != nil {
return fmt.Errorf("Failed to query home division: %v", err)
}
r := state.RootModule().Resources[resource]
if r == nil {
return fmt.Errorf("%s not found in state", resource)
}
a := r.Primary.Attributes
if a["division_id"] != homeDivID {
return fmt.Errorf("expected division to be home division %s", homeDivID)
}
return nil
}
}
package provider
import (
"context"
"log"
"sync"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// SDKClientPool holds a Pool of client configs for the Genesys Cloud SDK. One should be
// acquired at the beginning of any resource operation and released on completion.
// This has the benefit of ensuring we don't issue too many concurrent requests and also
// increases throughput as each token will have its own rate limit.
type SDKClientPool struct {
Pool chan *platformclientv2.Configuration
}
var SdkClientPool *SDKClientPool
var SdkClientPoolErr diag.Diagnostics
var Once sync.Once
// InitSDKClientPool creates a new Pool of Clients with the given provider config
// This must be called during provider initialization before the Pool is used
func InitSDKClientPool(max int, version string, providerConfig *schema.ResourceData) diag.Diagnostics {
Once.Do(func() {
log.Print("Initializing default SDK client.")
// Initialize the default config for tests and anything else that doesn't use the Pool
err := InitClientConfig(providerConfig, version, platformclientv2.GetDefaultConfiguration())
if err != nil {
SdkClientPoolErr = err
return
}
log.Printf("Initializing %d SDK clients in the Pool.", max)
SdkClientPool = &SDKClientPool{
Pool: make(chan *platformclientv2.Configuration, max),
}
SdkClientPoolErr = SdkClientPool.preFill(providerConfig, version)
})
return SdkClientPoolErr
}
func (p *SDKClientPool) preFill(providerConfig *schema.ResourceData, version string) diag.Diagnostics {
errorChan := make(chan diag.Diagnostics)
wgDone := make(chan bool)
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for i := 0; i < cap(p.Pool); i++ {
sdkConfig := platformclientv2.NewConfiguration()
wg.Add(1)
go func() {
defer wg.Done()
err := InitClientConfig(providerConfig, version, sdkConfig)
if err != nil {
select {
case <-ctx.Done():
case errorChan <- err:
}
cancel()
return
}
}()
p.Pool <- sdkConfig
}
go func() {
wg.Wait()
close(wgDone)
}()
// Wait until either WaitGroup is done or an error is received
select {
case <-wgDone:
return nil
case err := <-errorChan:
return err
}
}
func (p *SDKClientPool) acquire() *platformclientv2.Configuration {
return <-p.Pool
}
func (p *SDKClientPool) release(c *platformclientv2.Configuration) {
select {
case p.Pool <- c:
default:
// Pool is full. Don't put it back in the Pool
}
}
type resContextFunc func(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics
type GetAllConfigFunc func(context.Context, *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics)
type GetCustomConfigFunc func(context.Context, *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, *resourceExporter.DependencyResource, diag.Diagnostics)
func CreateWithPooledClient(method resContextFunc) schema.CreateContextFunc {
return schema.CreateContextFunc(runWithPooledClient(method))
}
func ReadWithPooledClient(method resContextFunc) schema.ReadContextFunc {
return schema.ReadContextFunc(runWithPooledClient(method))
}
func UpdateWithPooledClient(method resContextFunc) schema.UpdateContextFunc {
return schema.UpdateContextFunc(runWithPooledClient(method))
}
func DeleteWithPooledClient(method resContextFunc) schema.DeleteContextFunc {
return schema.DeleteContextFunc(runWithPooledClient(method))
}
// Inject a pooled SDK client connection into a resource method's meta argument
// and automatically return it to the Pool on completion
func runWithPooledClient(method resContextFunc) resContextFunc {
return func(ctx context.Context, r *schema.ResourceData, meta interface{}) diag.Diagnostics {
clientConfig := SdkClientPool.acquire()
defer SdkClientPool.release(clientConfig)
// Check if the request has been cancelled
select {
case <-ctx.Done():
return diag.FromErr(ctx.Err()) // Error somewhere, terminate
default:
}
// Copy to a new providerMeta object and set the sdk config
newMeta := *meta.(*ProviderMeta)
newMeta.ClientConfig = clientConfig
return method(ctx, r, &newMeta)
}
}
// Inject a pooled SDK client connection into an exporter's getAll* method
func GetAllWithPooledClient(method GetAllConfigFunc) resourceExporter.GetAllResourcesFunc {
return func(ctx context.Context) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
clientConfig := SdkClientPool.acquire()
defer SdkClientPool.release(clientConfig)
// Check if the request has been cancelled
select {
case <-ctx.Done():
return nil, diag.FromErr(ctx.Err()) // Error somewhere, terminate
default:
}
return method(ctx, clientConfig)
}
}
func GetAllWithPooledClientCustom(method GetCustomConfigFunc) resourceExporter.GetAllCustomResourcesFunc {
return func(ctx context.Context) (resourceExporter.ResourceIDMetaMap, *resourceExporter.DependencyResource, diag.Diagnostics) {
clientConfig := SdkClientPool.acquire()
defer SdkClientPool.release(clientConfig)
// Check if the request has been cancelled
select {
case <-ctx.Done():
return nil, nil, diag.FromErr(ctx.Err()) // Error somewhere, terminate
default:
}
return method(ctx, clientConfig)
}
}
package recording_media_retention_policy
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_recording_media_retention_policy.go contains the data source implementation
for the resource.
Note: This code should contain no code for doing the actual lookup in Genesys Cloud. Instead,
it should be added to the _proxy.go file for the class using our proxy pattern.
*/
func dataSourceRecordingMediaRetentionPolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
pp := getPolicyProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
policy, retryable, resp, err := pp.getPolicyByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting media retention policy %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no media retention policy found with name %s", name), resp))
}
d.SetId(*policy.Id)
return nil
})
}
package recording_media_retention_policy
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_recording_media_retention_policy_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *policyProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllPoliciesFunc func(ctx context.Context, p *policyProxy) (*[]platformclientv2.Policy, *platformclientv2.APIResponse, error)
type createPolicyFunc func(ctx context.Context, p *policyProxy, policyCreate *platformclientv2.Policycreate) (*platformclientv2.Policy, *platformclientv2.APIResponse, error)
type getPolicyByIdFunc func(ctx context.Context, p *policyProxy, policyId string) (policy *platformclientv2.Policy, response *platformclientv2.APIResponse, err error)
type getPolicyByNameFunc func(ctx context.Context, p *policyProxy, policyName string) (policy *platformclientv2.Policy, retryable bool, response *platformclientv2.APIResponse, err error)
type updatePolicyFunc func(ctx context.Context, p *policyProxy, policyId string, policy *platformclientv2.Policy) (*platformclientv2.Policy, *platformclientv2.APIResponse, error)
type deletePolicyFunc func(ctx context.Context, p *policyProxy, policyId string) (response *platformclientv2.APIResponse, err error)
type getFormsEvaluationFunc func(ctx context.Context, p *policyProxy, formId string) (*platformclientv2.Evaluationformresponse, *platformclientv2.APIResponse, error)
type getEvaluationFormRecentVerIdFunc func(ctx context.Context, p *policyProxy, formId string) (string, *platformclientv2.APIResponse, error)
type getQualityFormsSurveyByNameFunc func(ctx context.Context, p *policyProxy, surveyName string) (*platformclientv2.Publishedsurveyformreference, *platformclientv2.APIResponse, error)
// integrationProxy contains all of the methods that call genesys cloud APIs.
type policyProxy struct {
clientConfig *platformclientv2.Configuration
qualityApi *platformclientv2.QualityApi
recordingApi *platformclientv2.RecordingApi
getAllPoliciesAttr getAllPoliciesFunc
createPolicyAttr createPolicyFunc
getPolicyByIdAttr getPolicyByIdFunc
getPolicyByNameAttr getPolicyByNameFunc
updatePolicyAttr updatePolicyFunc
deletePolicyAttr deletePolicyFunc
getFormsEvaluationAttr getFormsEvaluationFunc
getEvaluationFormRecentVerIdAttr getEvaluationFormRecentVerIdFunc
getQualityFormsSurveyByNameAttr getQualityFormsSurveyByNameFunc
}
// newPolicyProxy initializes the Policy proxy with all of the data needed to communicate with Genesys Cloud
func newPolicyProxy(clientConfig *platformclientv2.Configuration) *policyProxy {
qApi := platformclientv2.NewQualityApiWithConfig(clientConfig)
rApi := platformclientv2.NewRecordingApiWithConfig(clientConfig)
return &policyProxy{
clientConfig: clientConfig,
qualityApi: qApi,
recordingApi: rApi,
getAllPoliciesAttr: getAllPoliciesFn,
createPolicyAttr: createPolicyFn,
getPolicyByIdAttr: getPolicyByIdFn,
getPolicyByNameAttr: getPolicyByNameFn,
updatePolicyAttr: updatePolicyFn,
deletePolicyAttr: deletePolicyFn,
getFormsEvaluationAttr: getFormsEvaluationFn,
getEvaluationFormRecentVerIdAttr: getEvaluationFormRecentVerIdFn,
getQualityFormsSurveyByNameAttr: getQualityFormsSurveyByNameFn,
}
}
// getPolicyProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getPolicyProxy(clientConfig *platformclientv2.Configuration) *policyProxy {
if internalProxy == nil {
internalProxy = newPolicyProxy(clientConfig)
}
return internalProxy
}
// getAllPolicies retrieves all Genesys Cloud Recording Media Retention Policies
func (p *policyProxy) getAllPolicies(ctx context.Context) (*[]platformclientv2.Policy, *platformclientv2.APIResponse, error) {
return p.getAllPoliciesAttr(ctx, p)
}
// createPolicy creates a Genesys Cloud Recording Media Retention Policy
func (p *policyProxy) createPolicy(ctx context.Context, policyCreate *platformclientv2.Policycreate) (*platformclientv2.Policy, *platformclientv2.APIResponse, error) {
return p.createPolicyAttr(ctx, p, policyCreate)
}
// getPolicyById gets a Genesys Cloud Recording Media Retention Policy by id
func (p *policyProxy) getPolicyById(ctx context.Context, policyId string) (policy *platformclientv2.Policy, response *platformclientv2.APIResponse, err error) {
return p.getPolicyByIdAttr(ctx, p, policyId)
}
// getPolicyByName gets a Genesys Cloud Recording Media Retention Policy by name
func (p *policyProxy) getPolicyByName(ctx context.Context, policyName string) (policy *platformclientv2.Policy, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getPolicyByNameAttr(ctx, p, policyName)
}
// updatePolicy updates a Genesys Cloud Recording Media Retention Policy
func (p *policyProxy) updatePolicy(ctx context.Context, policyId string, policy *platformclientv2.Policy) (*platformclientv2.Policy, *platformclientv2.APIResponse, error) {
return p.updatePolicyAttr(ctx, p, policyId, policy)
}
// deletePolicy deletes a Genesys Cloud Recording Media Retention Policy
func (p *policyProxy) deletePolicy(ctx context.Context, policyId string) (response *platformclientv2.APIResponse, err error) {
return p.deletePolicyAttr(ctx, p, policyId)
}
// getFormsEvaluation gets a Genesys Cloud Evaluation Form by id
func (p *policyProxy) getFormsEvaluation(ctx context.Context, formId string) (*platformclientv2.Evaluationformresponse, *platformclientv2.APIResponse, error) {
return p.getFormsEvaluationAttr(ctx, p, formId)
}
// getFormsEvaluation gets the most recent unpublished version id of a Genesys Cloud Evaluation Form
func (p *policyProxy) getEvaluationFormRecentVerId(ctx context.Context, formId string) (string, *platformclientv2.APIResponse, error) {
return p.getEvaluationFormRecentVerIdAttr(ctx, p, formId)
}
// getQualityFormsSurveyByName gets a Genesys Cloud Survey Form by name
func (p *policyProxy) getQualityFormsSurveyByName(ctx context.Context, surveyName string) (*platformclientv2.Publishedsurveyformreference, *platformclientv2.APIResponse, error) {
return p.getQualityFormsSurveyByNameAttr(ctx, p, surveyName)
}
// getAllIntegrationCredsFn is the implementation for getting all media retention policy in Genesys Cloud
func getAllPoliciesFn(ctx context.Context, p *policyProxy) (*[]platformclientv2.Policy, *platformclientv2.APIResponse, error) {
var allPolicies []platformclientv2.Policy
var response *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
retentionPolicies, resp, err := p.recordingApi.GetRecordingMediaretentionpolicies(pageSize, pageNum, "", []string{}, "", "", "", true, false, false, 0)
if err != nil {
return nil, resp, err
}
response = resp
if retentionPolicies.Entities == nil || len(*retentionPolicies.Entities) == 0 {
break
}
allPolicies = append(allPolicies, *retentionPolicies.Entities...)
}
return &allPolicies, response, nil
}
// createPolicyFn is the implementation for creating a media retention policy in Genesys Cloud
func createPolicyFn(ctx context.Context, p *policyProxy, policyCreate *platformclientv2.Policycreate) (*platformclientv2.Policy, *platformclientv2.APIResponse, error) {
policy, resp, err := p.recordingApi.PostRecordingMediaretentionpolicies(*policyCreate)
if err != nil {
return nil, resp, err
}
return policy, resp, nil
}
// getPolicyByIdFn is the implementation for getting a media retention policy in Genesys Cloud by id
func getPolicyByIdFn(ctx context.Context, p *policyProxy, policyId string) (policy *platformclientv2.Policy, response *platformclientv2.APIResponse, err error) {
policy, resp, err := p.recordingApi.GetRecordingMediaretentionpolicy(policyId)
if err != nil {
return nil, resp, err
}
return policy, resp, nil
}
// getPolicyByNameFn is the implementation for getting a media retention policy in Genesys Cloud by name
func getPolicyByNameFn(ctx context.Context, p *policyProxy, policyName string) (policy *platformclientv2.Policy, retryable bool, response *platformclientv2.APIResponse, err error) {
const pageSize = 100
const pageNum = 1
policies, resp, err := p.recordingApi.GetRecordingMediaretentionpolicies(pageSize, pageNum, "", nil, "", "", policyName, true, false, false, 0)
if err != nil {
return nil, false, resp, err
}
if policies.Entities == nil || len(*policies.Entities) == 0 {
return nil, true, resp, fmt.Errorf("no media retention policy found with name %s", policyName)
}
policy = &(*policies.Entities)[0]
return policy, false, resp, nil
}
// updatePolicyFn is the implementation for updating a media retention policy in Genesys Cloud
func updatePolicyFn(ctx context.Context, p *policyProxy, policyId string, policyBody *platformclientv2.Policy) (*platformclientv2.Policy, *platformclientv2.APIResponse, error) {
policy, resp, err := p.recordingApi.PutRecordingMediaretentionpolicy(policyId, *policyBody)
if err != nil {
return nil, resp, err
}
return policy, resp, nil
}
// deletePolicyFn is the implementation for deleting a media retention policy in Genesys Cloud
func deletePolicyFn(ctx context.Context, p *policyProxy, policyId string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.recordingApi.DeleteRecordingMediaretentionpolicy(policyId)
if err != nil {
return resp, err
}
return resp, nil
}
// getFormsEvaluationFn is the implementation for getting an evaluation form in Genesys Cloud
func getFormsEvaluationFn(ctx context.Context, p *policyProxy, formId string) (*platformclientv2.Evaluationformresponse, *platformclientv2.APIResponse, error) {
form, resp, err := p.qualityApi.GetQualityFormsEvaluation(formId)
if err != nil {
return nil, resp, err
}
return form, resp, nil
}
// getEvaluationFormRecentVerIdFn is the implementation for getting the most recent version if of an evaluation form in Genesys Cloud
func getEvaluationFormRecentVerIdFn(ctx context.Context, p *policyProxy, formId string) (string, *platformclientv2.APIResponse, error) {
formVersions, resp, err := p.qualityApi.GetQualityFormsEvaluationVersions(formId, 25, 1, "desc")
if err != nil {
return "", resp, err
}
if formVersions.Entities == nil || len(*formVersions.Entities) == 0 {
return "", resp, fmt.Errorf("no versions found for form %s", formId)
}
return *(*formVersions.Entities)[0].Id, resp, nil
}
// getQualityFormsSurveyByNameFn is the implementation for getting a survey form in Genesys Cloud
func getQualityFormsSurveyByNameFn(ctx context.Context, p *policyProxy, surveyName string) (*platformclientv2.Publishedsurveyformreference, *platformclientv2.APIResponse, error) {
const pageNum = 1
const pageSize = 100
forms, resp, err := p.qualityApi.GetQualityFormsSurveys(pageSize, pageNum, "", "", "", "", surveyName, "desc")
if err != nil {
return nil, resp, err
}
if forms.Entities == nil || len(*forms.Entities) == 0 {
return nil, resp, fmt.Errorf("no survey forms found with name %s", surveyName)
}
surveyFormReference := platformclientv2.Publishedsurveyformreference{Name: &surveyName, ContextId: (*forms.Entities)[0].ContextId}
return &surveyFormReference, resp, nil
}
package recording_media_retention_policy
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
gcloud "terraform-provider-genesyscloud/genesyscloud"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_recording_media_retention_policy.go contains all of the methods that perform the core logic for a resource.
In general a resource should have a approximately 5 methods in it:
1. A getAll.... function that the CX as Code exporter will use during the process of exporting Genesys Cloud.
2. A create.... function that the resource will use to create a Genesys Cloud object (e.g. genesyscloud_recording_media_retention_policy)
3. A read.... function that looks up a single resource.
4. An update... function that updates a single resource.
5. A delete.... function that deletes a single resource.
Two things to note:
1. All code in these methods should be focused on getting data in and out of Terraform. All code that is used for interacting
with a Genesys API should be encapsulated into a proxy class contained within the package.
2. In general, to keep this file somewhat manageable, if you find yourself with a number of helper functions move them to a
utils function in the package. This will keep the code manageable and easy to work through.
*/
// getAllMediaRetentionPolicies retrieves all of the recording media retention policies via Terraform in the Genesys Cloud and is used for the exporter
func getAllMediaRetentionPolicies(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
pp := getPolicyProxy(clientConfig)
retentionPolicies, resp, err := pp.getAllPolicies(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of media retention policies error: %s", err), resp)
}
for _, retentionPolicy := range *retentionPolicies {
resources[*retentionPolicy.Id] = &resourceExporter.ResourceMeta{Name: *retentionPolicy.Name}
}
return resources, nil
}
// createMediaRetentionPolicy is used by the recording media retention policy resource to create Genesyscloud a media retention policy
func createMediaRetentionPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPolicyProxy(sdkConfig)
name := d.Get("name").(string)
order := d.Get("order").(int)
description := d.Get("description").(string)
enabled := d.Get("enabled").(bool)
mediaPolicies := buildMediaPolicies(d, pp, ctx)
conditions := buildConditions(d)
actions := buildPolicyActionsFromResource(d, pp, ctx)
policyErrors := buildPolicyErrors(d)
reqBody := platformclientv2.Policycreate{
Name: &name,
Order: &order,
Description: &description,
Enabled: &enabled,
MediaPolicies: mediaPolicies,
Conditions: conditions,
Actions: actions,
PolicyErrors: policyErrors,
}
log.Printf("Creating media retention policy %s", name)
policy, resp, err := pp.createPolicy(ctx, &reqBody)
log.Printf("Media retention policy creation status %#v", resp.Status)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create media retention policy %s error: %s", name, err), resp)
}
// Make sure form is properly created
policyId := policy.Id
d.SetId(*policyId)
log.Printf("Created media retention policy %s %s", name, *policy.Id)
return readMediaRetentionPolicy(ctx, d, meta)
}
// readMediaRetentionPolicy is used by the recording media retention policy resource to read a media retention policy from genesys cloud.
func readMediaRetentionPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPolicyProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, gcloud.ResourceSurveyForm(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading media retention policy %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
retentionPolicy, resp, err := pp.getPolicyById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read media retention policy %s | error: %s", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read media retention policy %s | error: %s", d.Id(), err), resp))
}
resourcedata.SetNillableValue(d, "name", retentionPolicy.Name)
resourcedata.SetNillableValue(d, "order", retentionPolicy.Order)
resourcedata.SetNillableValue(d, "description", retentionPolicy.Description)
resourcedata.SetNillableValue(d, "enabled", retentionPolicy.Enabled)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "conditions", retentionPolicy.Conditions, flattenConditions)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "policy_errors", retentionPolicy.PolicyErrors, flattenPolicyErrors)
if retentionPolicy.MediaPolicies != nil {
d.Set("media_policies", flattenMediaPolicies(retentionPolicy.MediaPolicies, pp, ctx))
}
if retentionPolicy.Actions != nil {
d.Set("actions", flattenPolicyActions(retentionPolicy.Actions, pp, ctx))
}
return cc.CheckState(d)
})
}
// updateMediaRetentionPolicy is used by the recording media retention policy resource to update a media retention policy in Genesys Cloud
func updateMediaRetentionPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPolicyProxy(sdkConfig)
name := d.Get("name").(string)
order := d.Get("order").(int)
description := d.Get("description").(string)
enabled := d.Get("enabled").(bool)
mediaPolicies := buildMediaPolicies(d, pp, ctx)
conditions := buildConditions(d)
actions := buildPolicyActionsFromResource(d, pp, ctx)
policyErrors := buildPolicyErrors(d)
reqBody := platformclientv2.Policy{
Name: &name,
Order: &order,
Description: &description,
Enabled: &enabled,
MediaPolicies: mediaPolicies,
Conditions: conditions,
Actions: actions,
PolicyErrors: policyErrors,
}
log.Printf("Updating media retention policy %s", name)
policy, resp, err := pp.updatePolicy(ctx, d.Id(), &reqBody)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update media retention policy %s error: %s", name, err), resp)
}
log.Printf("Updated media retention policy %s %s", name, *policy.Id)
return readMediaRetentionPolicy(ctx, d, meta)
}
// deleteMediaRetentionPolicy is used by the recording media retention policy resource to delete a media retention policy from Genesys cloud.
func deleteMediaRetentionPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPolicyProxy(sdkConfig)
log.Printf("Deleting media retention policy %s", name)
resp, err := pp.deletePolicy(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete media retention policy %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := pp.getPolicyById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// media retention policy deleted
log.Printf("Deleted media retention policy %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting media retention policy %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("media retention policy %s still exists", d.Id()), resp))
})
}
package recording_media_retention_policy
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
The genesyscloud_recording_media_retention_policy_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the genesyscloud_recording_media_retention_policy resource.
3. The datasource schema definitions for the genesyscloud_recording_media_retention_policy datasource.
4. The resource exporter configuration for the genesyscloud_recording_media_retention_policy exporter.
*/
const resourceName = "genesyscloud_recording_media_retention_policy"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceRecordingMediaRetentionPolicy())
l.RegisterResource(resourceName, ResourceMediaRetentionPolicy())
l.RegisterExporter(resourceName, MediaRetentionPolicyExporter())
}
// ResourceMediaRetentionPolicy registers the genesyscloud_recording_media_retention_policy resource with Terraform
func ResourceMediaRetentionPolicy() *schema.Resource {
timeSlot := &schema.Resource{
Schema: map[string]*schema.Schema{
"start_time": {
Description: "start time in xx:xx:xx.xxx format",
Type: schema.TypeString,
Optional: true,
},
"stop_time": {
Description: "stop time in xx:xx:xx.xxx format",
Type: schema.TypeString,
Optional: true,
},
"day": {
Description: "Day for this time slot, Monday = 1 ... Sunday = 7",
Type: schema.TypeInt,
Optional: true,
},
},
}
timeAllowed := &schema.Resource{
Schema: map[string]*schema.Schema{
"time_slots": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: timeSlot,
},
"time_zone_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"empty": {
Description: "",
Type: schema.TypeBool,
Optional: true,
},
},
}
durationCondition := &schema.Resource{
Schema: map[string]*schema.Schema{
"duration_target": {
Description: "",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"DURATION", "DURATION_RANGE"}, false),
},
"duration_operator": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"duration_range": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"duration_mode": {
Description: "",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Between", "Over", "Under"}, false),
},
},
}
callMediaPolicyConditions := &schema.Resource{
Schema: map[string]*schema.Schema{
"for_user_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"date_ranges": {
Description: "",
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{Type: schema.TypeString},
},
"for_queue_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"wrapup_code_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"language_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"time_allowed": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: timeAllowed,
},
"directions": {
Description: "",
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{Type: schema.TypeString},
},
"duration": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: durationCondition,
},
},
}
chatMediaPolicyConditions := &schema.Resource{
Schema: map[string]*schema.Schema{
"for_user_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"date_ranges": {
Description: "",
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{Type: schema.TypeString},
},
"for_queue_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"wrapup_code_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"language_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"time_allowed": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: timeAllowed,
},
"duration": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: durationCondition,
},
},
}
emailMediaPolicyConditions := &schema.Resource{
Schema: map[string]*schema.Schema{
"for_user_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"date_ranges": {
Description: "",
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{Type: schema.TypeString},
},
"for_queue_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"wrapup_code_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"language_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"time_allowed": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: timeAllowed,
},
},
}
messageMediaPolicyConditions := &schema.Resource{
Schema: map[string]*schema.Schema{
"for_user_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"date_ranges": {
Description: "",
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{Type: schema.TypeString},
},
"for_queue_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"wrapup_code_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"language_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"time_allowed": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: timeAllowed,
},
},
}
policyConditions := &schema.Resource{
Schema: map[string]*schema.Schema{
"for_user_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"directions": {
Description: "",
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{Type: schema.TypeString},
},
"date_ranges": {
Description: "",
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{Type: schema.TypeString},
},
"media_types": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"for_queue_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"duration": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: durationCondition,
},
"wrapup_code_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"time_allowed": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: timeAllowed,
},
},
}
userParam := &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"value": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
},
}
archiveRetention := &schema.Resource{
Schema: map[string]*schema.Schema{
"days": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
"storage_medium": {
Description: "",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"CLOUDARCHIVE"}, false)},
},
}
deleteRetention := &schema.Resource{
Schema: map[string]*schema.Schema{
"days": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
},
}
policyErrorMessage := &schema.Resource{
Schema: map[string]*schema.Schema{
"status_code": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
"user_message": {
Description: "",
Type: schema.TypeMap,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"user_params_message": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"error_code": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"correlation_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"user_params": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: userParam,
},
"insert_date": {
Description: "Date time is represented as an ISO-8601 string. For example: yyyy-MM-ddTHH:mm:ss[.mmm]Z",
Type: schema.TypeString,
Optional: true,
},
},
}
agentTimeInterval := &schema.Resource{
Schema: map[string]*schema.Schema{
"months": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
"weeks": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
"days": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
},
}
evalTimeInterval := &schema.Resource{
Schema: map[string]*schema.Schema{
"hours": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
"days": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
},
}
policyErrors := &schema.Resource{
Schema: map[string]*schema.Schema{
"policy_error_messages": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: policyErrorMessage,
},
},
}
evaluationAssignment := &schema.Resource{
Schema: map[string]*schema.Schema{
"evaluation_form_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"user_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
},
}
meteredEvaluationAssignment := &schema.Resource{
Schema: map[string]*schema.Schema{
"evaluator_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"max_number_evaluations": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
"evaluation_form_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"assign_to_active_user": {
Description: "",
Type: schema.TypeBool,
Optional: true,
},
"time_interval": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: evalTimeInterval,
},
},
}
meteredAssignmentByAgent := &schema.Resource{
Schema: map[string]*schema.Schema{
"evaluator_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"max_number_evaluations": {
Description: "",
Type: schema.TypeInt,
Optional: true,
},
"evaluation_form_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"time_interval": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: agentTimeInterval,
},
"time_zone": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
},
}
calibrationAssignment := &schema.Resource{
Schema: map[string]*schema.Schema{
"calibrator_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"evaluator_ids": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"evaluation_form_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"expert_evaluator_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
},
}
surveyAssignment := &schema.Resource{
Schema: map[string]*schema.Schema{
"survey_form_name": {
Description: "The survey form used for this survey.",
Type: schema.TypeString,
Optional: true,
},
"flow_id": {
Description: "The UUID reference to the flow associated with this survey.",
Type: schema.TypeString,
Optional: true,
},
"invite_time_interval": {
Description: "An ISO 8601 repeated interval consisting of the number of repetitions, the start datetime, and the interval (e.g. R2/2018-03-01T13:00:00Z/P1M10DT2H30M). Total duration must not exceed 90 days.",
Type: schema.TypeString,
Default: "R1/P0M",
Optional: true,
},
"sending_user": {
Description: "User together with sendingDomain used to send email, null to use no-reply",
Type: schema.TypeString,
Optional: true,
},
"sending_domain": {
Description: "Validated email domain, required",
Type: schema.TypeString,
Required: true,
},
},
}
retentionDuration := &schema.Resource{
Schema: map[string]*schema.Schema{
"archive_retention": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: archiveRetention,
},
"delete_retention": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: deleteRetention,
},
},
}
initiateScreenRecording := &schema.Resource{
Schema: map[string]*schema.Schema{
"record_acw": {
Description: "",
Type: schema.TypeBool,
Optional: true,
},
"archive_retention": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: archiveRetention,
},
"delete_retention": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: deleteRetention,
},
},
}
mediaTranscription := &schema.Resource{
Schema: map[string]*schema.Schema{
"display_name": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
"transcription_provider": {
Description: "",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"VOCI", "CALLJOURNEY"}, false),
},
"integration_id": {
Description: "",
Type: schema.TypeString,
Optional: true,
},
},
}
integrationExport := &schema.Resource{
Schema: map[string]*schema.Schema{
"integration_id": {
Description: "The aws-s3-recording-bulk-actions-integration that the policy uses for exports.",
Type: schema.TypeString,
Optional: true,
},
"should_export_screen_recordings": {
Description: "True if the policy should export screen recordings in addition to the other conversation media.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}
policyActions := &schema.Resource{
Schema: map[string]*schema.Schema{
"retain_recording": {
Description: "true to retain the recording associated with the conversation.",
Type: schema.TypeBool,
Optional: true,
},
"delete_recording": {
Description: "true to delete the recording associated with the conversation. If retainRecording = true, this will be ignored.",
Type: schema.TypeBool,
Optional: true,
},
"always_delete": {
Description: "true to delete the recording associated with the conversation regardless of the values of retainRecording or deleteRecording.",
Type: schema.TypeBool,
Optional: true,
},
"assign_evaluations": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: evaluationAssignment,
},
"assign_metered_evaluations": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: meteredEvaluationAssignment,
},
"assign_metered_assignment_by_agent": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: meteredAssignmentByAgent,
},
"assign_calibrations": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: calibrationAssignment,
},
"assign_surveys": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: surveyAssignment,
},
"retention_duration": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: retentionDuration,
},
"initiate_screen_recording": {
Description: "",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: initiateScreenRecording,
},
"media_transcriptions": {
Description: "",
Type: schema.TypeList,
Optional: true,
Elem: mediaTranscription,
},
"integration_export": {
Description: "Policy action for exporting recordings using an integration to 3rd party s3.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: integrationExport,
},
},
}
callMediaPolicy := &schema.Resource{
Schema: map[string]*schema.Schema{
"actions": {
Description: "Actions applied when specified conditions are met",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: policyActions,
},
"conditions": {
Description: "Conditions for when actions should be applied",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: callMediaPolicyConditions,
},
},
}
chatMediaPolicy := &schema.Resource{
Schema: map[string]*schema.Schema{
"actions": {
Description: "Actions applied when specified conditions are met",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: policyActions,
},
"conditions": {
Description: "Conditions for when actions should be applied",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: chatMediaPolicyConditions,
},
},
}
emailMediaPolicy := &schema.Resource{
Schema: map[string]*schema.Schema{
"actions": {
Description: "Actions applied when specified conditions are met",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: policyActions,
},
"conditions": {
Description: "Conditions for when actions should be applied",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: emailMediaPolicyConditions,
},
},
}
messageMediaPolicy := &schema.Resource{
Schema: map[string]*schema.Schema{
"actions": {
Description: "Actions applied when specified conditions are met",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: policyActions,
},
"conditions": {
Description: "Conditions for when actions should be applied",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: messageMediaPolicyConditions,
},
},
}
mediaPolicies := &schema.Resource{
Schema: map[string]*schema.Schema{
"call_policy": {
Description: "Conditions and actions for calls",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: callMediaPolicy,
},
"chat_policy": {
Description: "Conditions and actions for calls",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: chatMediaPolicy,
},
"email_policy": {
Description: "Conditions and actions for calls",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: emailMediaPolicy,
},
"message_policy": {
Description: "Conditions and actions for calls",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: messageMediaPolicy,
},
},
}
return &schema.Resource{
Description: "Genesys Cloud Media Retention Policies",
CreateContext: provider.CreateWithPooledClient(createMediaRetentionPolicy),
ReadContext: provider.ReadWithPooledClient(readMediaRetentionPolicy),
UpdateContext: provider.UpdateWithPooledClient(updateMediaRetentionPolicy),
DeleteContext: provider.DeleteWithPooledClient(deleteMediaRetentionPolicy),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The policy name. Changing the policy_name attribute will cause the recording_media_retention_policy to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"order": {
Description: "The ordinal number for the policy",
Type: schema.TypeInt,
Optional: true,
},
"description": {
Description: "The description for the policy",
Type: schema.TypeString,
Optional: true,
},
"enabled": {
Description: "The policy will be enabled if true, otherwise it will be disabled",
Type: schema.TypeBool,
Optional: true,
},
"media_policies": {
Description: "Conditions and actions per media type",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: mediaPolicies,
},
"conditions": {
Description: "Conditions",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: policyConditions,
},
"actions": {
Description: "Actions",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: policyActions,
},
"policy_errors": {
Description: "A list of errors in the policy configuration",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: policyErrors,
},
},
}
}
// MediaRetentionPolicyExporter returns the resourceExporter object used to hold the genesyscloud_recording_media_retention_policy exporter's config
func MediaRetentionPolicyExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllMediaRetentionPolicies),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"media_policies.chat_policy.conditions.for_queue_ids": {RefType: "genesyscloud_routing_queue", AltValues: []string{"*"}},
"media_policies.call_policy.conditions.for_queue_ids": {RefType: "genesyscloud_routing_queue", AltValues: []string{"*"}},
"media_policies.message_policy.conditions.for_queue_ids": {RefType: "genesyscloud_routing_queue", AltValues: []string{"*"}},
"media_policies.email_policy.conditions.for_queue_ids": {RefType: "genesyscloud_routing_queue", AltValues: []string{"*"}},
"conditions.for_queue_ids": {RefType: "genesyscloud_routing_queue", AltValues: []string{"*"}},
"media_policies.call_policy.conditions.for_user_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.chat_policy.conditions.for_user_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.email_policy.conditions.for_user_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.message_policy.conditions.for_user_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"conditions.for_user_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.call_policy.actions.assign_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.call_policy.actions.assign_calibrations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.call_policy.actions.assign_metered_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.call_policy.actions.assign_metered_assignment_by_agent.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.chat_policy.actions.assign_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.chat_policy.actions.assign_calibrations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.chat_policy.actions.assign_metered_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.chat_policy.actions.assign_metered_assignment_by_agent.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.message_policy.actions.assign_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.message_policy.actions.assign_calibrations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.message_policy.actions.assign_metered_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.message_policy.actions.assign_metered_assignment_by_agent.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.email_policy.actions.assign_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.email_policy.actions.assign_calibrations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.email_policy.actions.assign_metered_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.email_policy.actions.assign_metered_assignment_by_agent.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"actions.assign_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"actions.assign_calibrations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"actions.assign_metered_evaluations.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"actions.assign_metered_assignment_by_agent.evaluation_form_id": {RefType: "genesyscloud_quality_forms_evaluation"},
"media_policies.call_policy.actions.assign_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.call_policy.actions.assign_calibrations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.call_policy.actions.assign_metered_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.call_policy.actions.assign_metered_assignment_by_agent.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.chat_policy.actions.assign_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.chat_policy.actions.assign_calibrations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.chat_policy.actions.assign_metered_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.chat_policy.actions.assign_metered_assignment_by_agent.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.message_policy.actions.assign_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.message_policy.actions.assign_calibrations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.message_policy.actions.assign_metered_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.message_policy.actions.assign_metered_assignment_by_agent.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.email_policy.actions.assign_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.email_policy.actions.assign_calibrations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.email_policy.actions.assign_metered_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.email_policy.actions.assign_metered_assignment_by_agent.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"actions.assign_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"actions.assign_calibrations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"actions.assign_metered_evaluations.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"actions.assign_metered_assignment_by_agent.evaluator_ids": {RefType: "genesyscloud_user", AltValues: []string{"*"}},
"media_policies.call_policy.actions.assign_calibrations.calibrator_id": {RefType: "genesyscloud_user"},
"media_policies.chat_policy.actions.assign_calibrations.calibrator_id": {RefType: "genesyscloud_user"},
"media_policies.message_policy.actions.assign_calibrations.calibrator_id": {RefType: "genesyscloud_user"},
"media_policies.email_policy.actions.assign_calibrations.calibrator_id": {RefType: "genesyscloud_user"},
"media_policies.call_policy.actions.assign_calibrations.expert_evaluator_id": {RefType: "genesyscloud_user"},
"media_policies.chat_policy.actions.assign_calibrations.expert_evaluator_id": {RefType: "genesyscloud_user"},
"media_policies.message_policy.actions.assign_calibrations.expert_evaluator_id": {RefType: "genesyscloud_user"},
"media_policies.email_policy.actions.assign_calibrations.expert_evaluator_id": {RefType: "genesyscloud_user"},
"actions.assign_calibrations.expert_evaluator_id": {RefType: "genesyscloud_user"},
"media_policies.call_policy.conditions.language_ids": {RefType: "genesyscloud_routing_language", AltValues: []string{"*"}},
"media_policies.chat_policy.conditions.language_ids": {RefType: "genesyscloud_routing_language", AltValues: []string{"*"}},
"media_policies.message_policy.conditions.language_ids": {RefType: "genesyscloud_routing_language", AltValues: []string{"*"}},
"media_policies.email_policy.conditions.language_ids": {RefType: "genesyscloud_routing_language", AltValues: []string{"*"}},
"media_policies.call_policy.conditions.wrapup_code_ids": {RefType: "genesyscloud_routing_wrapupcode", AltValues: []string{"*"}},
"media_policies.chat_policy.conditions.wrapup_code_ids": {RefType: "genesyscloud_routing_wrapupcode", AltValues: []string{"*"}},
"media_policies.message_policy.conditions.wrapup_code_ids": {RefType: "genesyscloud_routing_wrapupcode", AltValues: []string{"*"}},
"media_policies.email_policy.conditions.wrapup_code_ids": {RefType: "genesyscloud_routing_wrapupcode", AltValues: []string{"*"}},
"conditions.wrapup_code_ids": {RefType: "genesyscloud_routing_wrapupcode", AltValues: []string{"*"}},
"media_policies.call_policy.actions.integration_export.integration_id": {RefType: "genesyscloud_integration"},
"media_policies.chat_policy.actions.integration_export.integration_id": {RefType: "genesyscloud_integration"},
"media_policies.message_policy.actions.integration_export.integration_id": {RefType: "genesyscloud_integration"},
"media_policies.email_policy.actions.integration_export.integration_id": {RefType: "genesyscloud_integration"},
"actions.media_transcriptions.integration_id": {RefType: "genesyscloud_integration"},
"media_policies.call_policy.actions.assign_surveys.flow_id": {RefType: "genesyscloud_flow"},
"media_policies.chat_policy.actions.assign_surveys.flow_id": {RefType: "genesyscloud_flow"},
"media_policies.message_policy.actions.assign_surveys.flow_id": {RefType: "genesyscloud_flow"},
"media_policies.email_policy.actions.assign_surveys.flow_id": {RefType: "genesyscloud_flow"},
"actions.assign_surveys.flow_id": {RefType: "genesyscloud_flow"},
"media_policies.call_policy.actions.assign_evaluations.user_id": {RefType: "genesyscloud_user"},
"media_policies.chat_policy.actions.assign_evaluations.user_id": {RefType: "genesyscloud_user"},
"media_policies.message_policy.actions.assign_evaluations.user_id": {RefType: "genesyscloud_user"},
"media_policies.email_policy.actions.assign_evaluations.user_id": {RefType: "genesyscloud_user"},
"actions.assign_evaluations.user_id": {RefType: "genesyscloud_user"},
},
AllowZeroValues: []string{"order"},
RemoveIfMissing: map[string][]string{
"": {"conditions", "actions"},
"media_policies": {"call_policy", "chat_policy", "message_policy", "email_policy"},
},
}
}
// DataSourceRecordingMediaRetentionPolicy registers the genesyscloud_recording_media_retention_policy data source
func DataSourceRecordingMediaRetentionPolicy() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud media retention policy. Select a policy by name",
ReadContext: provider.ReadWithPooledClient(dataSourceRecordingMediaRetentionPolicyRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Media retention policy name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package recording_media_retention_policy
import (
"context"
"fmt"
"log"
"reflect"
"time"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_recording_media_retention_policy_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
Note: Look for opportunities to minimize boilerplate code using functions and Generics
*/
type VisibilityConditionStruct struct {
CombiningOperation string
Predicates []string
}
func buildEvaluationAssignments(evaluations []interface{}, pp *policyProxy, ctx context.Context) *[]platformclientv2.Evaluationassignment {
assignEvaluations := make([]platformclientv2.Evaluationassignment, 0)
for _, assignEvaluation := range evaluations {
assignEvaluationMap, ok := assignEvaluation.(map[string]interface{})
if !ok {
continue
}
evaluationFormId := assignEvaluationMap["evaluation_form_id"].(string)
userId := assignEvaluationMap["user_id"].(string)
assignment := platformclientv2.Evaluationassignment{}
// if evaluation form id is present, get the context id and build the evaluation form
if evaluationFormId != "" {
form, resp, err := pp.getFormsEvaluation(ctx, evaluationFormId)
if err != nil {
log.Fatalf("failed to read evaluation form %s: %s %v", evaluationFormId, err, resp)
} else {
evaluationFormContextId := form.ContextId
assignment.EvaluationForm = &platformclientv2.Evaluationform{Id: &evaluationFormId, ContextId: evaluationFormContextId}
}
}
if userId != "" {
assignment.User = &platformclientv2.User{Id: &userId}
}
assignEvaluations = append(assignEvaluations, assignment)
}
return &assignEvaluations
}
func flattenEvaluationAssignments(assignments *[]platformclientv2.Evaluationassignment, pp *policyProxy, ctx context.Context) []interface{} {
if assignments == nil {
return nil
}
evaluationAssignments := make([]interface{}, 0)
for _, assignment := range *assignments {
assignmentMap := make(map[string]interface{})
// if form is present in the response, assign the most recent unpublished version id to align with evaluation form resource behavior for export purposes.
if assignment.EvaluationForm != nil {
formId := *assignment.EvaluationForm.Id
formVersionId, resp, err := pp.getEvaluationFormRecentVerId(ctx, formId)
if err != nil {
log.Fatalf("Failed to get evaluation form versions %s %v", *assignment.EvaluationForm.Name, resp)
} else {
formId = formVersionId
}
assignmentMap["evaluation_form_id"] = formId
}
if assignment.User != nil {
assignmentMap["user_id"] = *assignment.User.Id
}
evaluationAssignments = append(evaluationAssignments, assignmentMap)
}
return evaluationAssignments
}
func buildMeteredEvaluationsTimeInterval(interval []interface{}) *platformclientv2.Timeinterval {
var timeInterval platformclientv2.Timeinterval
if interval == nil || len(interval) <= 0 || (len(interval) == 1 && interval[0] == nil) {
return nil
}
timeIntervalMap, ok := interval[0].(map[string]interface{})
if !ok {
return nil
}
if days, ok := timeIntervalMap["days"].(int); ok && days != 0 {
timeInterval.Days = &days
}
if hours, ok := timeIntervalMap["hours"].(int); ok && hours != 0 {
timeInterval.Hours = &hours
}
return &timeInterval
}
func buildMeteredAssignmentByAgentTimeInterval(interval []interface{}) *platformclientv2.Timeinterval {
var timeInterval platformclientv2.Timeinterval
if interval == nil || len(interval) <= 0 || (len(interval) == 1 && interval[0] == nil) {
return nil
}
timeIntervalMap, ok := interval[0].(map[string]interface{})
if !ok {
return nil
}
if months, ok := timeIntervalMap["months"].(int); ok && months != 0 {
timeInterval.Months = &months
}
if weeks, ok := timeIntervalMap["weeks"].(int); ok && weeks != 0 {
timeInterval.Weeks = &weeks
}
if days, ok := timeIntervalMap["days"].(int); ok && days != 0 {
timeInterval.Days = &days
}
return &timeInterval
}
func flattenEvalTimeInterval(timeInterval *platformclientv2.Timeinterval) []interface{} {
if timeInterval == nil {
return nil
}
timeIntervalMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(timeIntervalMap, "days", timeInterval.Days)
resourcedata.SetMapValueIfNotNil(timeIntervalMap, "hours", timeInterval.Hours)
return []interface{}{timeIntervalMap}
}
func flattenAgentTimeInterval(timeInterval *platformclientv2.Timeinterval) []interface{} {
if timeInterval == nil {
return nil
}
timeIntervalMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(timeIntervalMap, "months", timeInterval.Months)
resourcedata.SetMapValueIfNotNil(timeIntervalMap, "weeks", timeInterval.Weeks)
resourcedata.SetMapValueIfNotNil(timeIntervalMap, "days", timeInterval.Days)
return []interface{}{timeIntervalMap}
}
func buildAssignMeteredEvaluations(assignments []interface{}, pp *policyProxy, ctx context.Context) *[]platformclientv2.Meteredevaluationassignment {
meteredAssignments := make([]platformclientv2.Meteredevaluationassignment, 0)
for _, assignment := range assignments {
assignmentMap, ok := assignment.(map[string]interface{})
if !ok {
continue
}
maxNumberEvaluations := assignmentMap["max_number_evaluations"].(int)
assignToActiveUser := assignmentMap["assign_to_active_user"].(bool)
evaluationFormId := assignmentMap["evaluation_form_id"].(string)
evaluatorIds := assignmentMap["evaluator_ids"].([]interface{})
idStrings := make([]string, 0)
for _, evaluatorId := range evaluatorIds {
idStrings = append(idStrings, fmt.Sprintf("%v", evaluatorId))
}
evaluators := make([]platformclientv2.User, 0)
for _, evaluatorId := range idStrings {
evaluator := evaluatorId
evaluators = append(evaluators, platformclientv2.User{Id: &evaluator})
}
timeInterval := buildMeteredEvaluationsTimeInterval(assignmentMap["time_interval"].([]interface{}))
temp := platformclientv2.Meteredevaluationassignment{
Evaluators: &evaluators,
MaxNumberEvaluations: &maxNumberEvaluations,
AssignToActiveUser: &assignToActiveUser,
TimeInterval: timeInterval,
}
// if evaluation form id is present, get the context id and build the evaluation form
if evaluationFormId != "" {
form, resp, err := pp.getFormsEvaluation(ctx, evaluationFormId)
if err != nil {
log.Fatalf("failed to read media evaluation form %s: %s %v", evaluationFormId, err, resp)
} else {
evaluationFormContextId := form.ContextId
temp.EvaluationForm = &platformclientv2.Evaluationform{Id: &evaluationFormId, ContextId: evaluationFormContextId}
}
}
meteredAssignments = append(meteredAssignments, temp)
}
return &meteredAssignments
}
func flattenAssignMeteredEvaluations(assignments *[]platformclientv2.Meteredevaluationassignment, pp *policyProxy, ctx context.Context) []interface{} {
if assignments == nil {
return nil
}
meteredAssignments := make([]interface{}, 0)
for _, assignment := range *assignments {
assignmentMap := make(map[string]interface{})
if assignment.Evaluators != nil {
evaluatorIds := make([]string, 0)
for _, evaluator := range *assignment.Evaluators {
evaluatorIds = append(evaluatorIds, *evaluator.Id)
}
assignmentMap["evaluator_ids"] = evaluatorIds
}
resourcedata.SetMapValueIfNotNil(assignmentMap, "max_number_evaluations", assignment.MaxNumberEvaluations)
// if form is present in the response, assign the most recent unpublished version id to align with evaluation form resource behavior for export purposes.
if assignment.EvaluationForm != nil {
formId := *assignment.EvaluationForm.Id
formVersionId, resp, err := pp.getEvaluationFormRecentVerId(ctx, formId)
if err != nil {
log.Fatalf("Failed to get evaluation form versions %s %v", *assignment.EvaluationForm.Name, resp)
} else {
formId = formVersionId
}
assignmentMap["evaluation_form_id"] = formId
}
resourcedata.SetMapValueIfNotNil(assignmentMap, "assign_to_active_user", assignment.AssignToActiveUser)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(assignmentMap, "time_interval", assignment.TimeInterval, flattenEvalTimeInterval)
meteredAssignments = append(meteredAssignments, assignmentMap)
}
return meteredAssignments
}
func buildAssignMeteredAssignmentByAgent(assignments []interface{}, pp *policyProxy, ctx context.Context) *[]platformclientv2.Meteredassignmentbyagent {
meteredAssignments := make([]platformclientv2.Meteredassignmentbyagent, 0)
for _, assignment := range assignments {
assignmentMap, ok := assignment.(map[string]interface{})
if !ok {
continue
}
maxNumberEvaluations := assignmentMap["max_number_evaluations"].(int)
timeZone := assignmentMap["time_zone"].(string)
evaluationFormId := assignmentMap["evaluation_form_id"].(string)
evaluatorIds := assignmentMap["evaluator_ids"].([]interface{})
idStrings := make([]string, 0)
for _, evaluatorId := range evaluatorIds {
idStrings = append(idStrings, fmt.Sprintf("%v", evaluatorId))
}
evaluators := make([]platformclientv2.User, 0)
for _, evaluatorId := range idStrings {
evaluator := evaluatorId
evaluators = append(evaluators, platformclientv2.User{Id: &evaluator})
}
timeInterval := buildMeteredAssignmentByAgentTimeInterval(assignmentMap["time_interval"].([]interface{}))
temp := platformclientv2.Meteredassignmentbyagent{
Evaluators: &evaluators,
MaxNumberEvaluations: &maxNumberEvaluations,
TimeInterval: timeInterval,
TimeZone: &timeZone,
}
// if evaluation form id is present, get the context id and build the evaluation form
if evaluationFormId != "" {
form, resp, err := pp.getFormsEvaluation(ctx, evaluationFormId)
if err != nil {
log.Fatalf("failed to read evaluation form %s: %s %v", evaluationFormId, err, resp)
} else {
evaluationFormContextId := form.ContextId
temp.EvaluationForm = &platformclientv2.Evaluationform{Id: &evaluationFormId, ContextId: evaluationFormContextId}
}
}
meteredAssignments = append(meteredAssignments, temp)
}
return &meteredAssignments
}
func flattenAssignMeteredAssignmentByAgent(assignments *[]platformclientv2.Meteredassignmentbyagent, pp *policyProxy, ctx context.Context) []interface{} {
if assignments == nil {
return nil
}
meteredAssignments := make([]interface{}, 0)
for _, assignment := range *assignments {
assignmentMap := make(map[string]interface{})
if assignment.Evaluators != nil {
evaluatorIds := make([]string, 0)
for _, evaluator := range *assignment.Evaluators {
evaluatorIds = append(evaluatorIds, *evaluator.Id)
}
assignmentMap["evaluator_ids"] = evaluatorIds
}
resourcedata.SetMapValueIfNotNil(assignmentMap, "max_number_evaluations", assignment.MaxNumberEvaluations)
// if form is present in the response, assign the most recent unpublished version id to align with evaluation form resource behavior for export purposes.
if assignment.EvaluationForm != nil {
formId := *assignment.EvaluationForm.Id
formVersionId, resp, err := pp.getEvaluationFormRecentVerId(ctx, formId)
if err != nil {
log.Fatalf("Failed to get evaluation form versions %s %v", *assignment.EvaluationForm.Name, resp)
} else {
formId = formVersionId
}
assignmentMap["evaluation_form_id"] = formId
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(assignmentMap, "time_interval", assignment.TimeInterval, flattenAgentTimeInterval)
resourcedata.SetMapValueIfNotNil(assignmentMap, "time_zone", assignment.TimeZone)
meteredAssignments = append(meteredAssignments, assignmentMap)
}
return meteredAssignments
}
func buildAssignCalibrations(assignments []interface{}, pp *policyProxy, ctx context.Context) *[]platformclientv2.Calibrationassignment {
calibrationAssignments := make([]platformclientv2.Calibrationassignment, 0)
for _, assignment := range assignments {
assignmentMap, ok := assignment.(map[string]interface{})
if !ok {
continue
}
evaluationFormId := assignmentMap["evaluation_form_id"].(string)
calibratorId := assignmentMap["calibrator_id"].(string)
expertEvaluatorId := assignmentMap["expert_evaluator_id"].(string)
evaluatorIds := assignmentMap["evaluator_ids"].([]interface{})
idStrings := make([]string, 0)
for _, evaluatorId := range evaluatorIds {
idStrings = append(idStrings, fmt.Sprintf("%v", evaluatorId))
}
evaluators := make([]platformclientv2.User, 0)
for _, evaluatorId := range idStrings {
id := evaluatorId
evaluators = append(evaluators, platformclientv2.User{Id: &id})
}
temp := platformclientv2.Calibrationassignment{
Evaluators: &evaluators,
}
// if evaluation form id is present, get the context id and build the evaluation form
if evaluationFormId != "" {
form, resp, err := pp.getFormsEvaluation(ctx, evaluationFormId)
if err != nil {
log.Fatalf("failed to read evaluation form %s: %s %v", evaluationFormId, err, resp)
} else {
evaluationFormContextId := form.ContextId
temp.EvaluationForm = &platformclientv2.Evaluationform{Id: &evaluationFormId, ContextId: evaluationFormContextId}
}
}
if calibratorId != "" {
temp.Calibrator = &platformclientv2.User{Id: &calibratorId}
}
if expertEvaluatorId != "" {
temp.ExpertEvaluator = &platformclientv2.User{Id: &expertEvaluatorId}
}
calibrationAssignments = append(calibrationAssignments, temp)
}
return &calibrationAssignments
}
func flattenAssignCalibrations(assignments *[]platformclientv2.Calibrationassignment, pp *policyProxy, ctx context.Context) []interface{} {
if assignments == nil {
return nil
}
calibrationAssignments := make([]interface{}, 0)
for _, assignment := range *assignments {
assignmentMap := make(map[string]interface{})
if assignment.Calibrator != nil {
assignmentMap["calibrator_id"] = *assignment.Calibrator.Id
}
if assignment.Evaluators != nil {
evaluatorIds := make([]string, 0)
for _, evaluator := range *assignment.Evaluators {
evaluatorIds = append(evaluatorIds, *evaluator.Id)
}
assignmentMap["evaluator_ids"] = evaluatorIds
}
// if form is present in the response, assign the most recent unpublished version id to align with evaluation form resource behavior for export purposes.
if assignment.EvaluationForm != nil {
formId := *assignment.EvaluationForm.Id
formVersionId, resp, err := pp.getEvaluationFormRecentVerId(ctx, formId)
if err != nil {
log.Fatalf("Failed to get evaluation form versions %s %v", *assignment.EvaluationForm.Name, resp)
} else {
formId = formVersionId
}
assignmentMap["evaluation_form_id"] = formId
}
if assignment.ExpertEvaluator != nil {
assignmentMap["expert_evaluator_id"] = *assignment.ExpertEvaluator.Id
}
calibrationAssignments = append(calibrationAssignments, assignmentMap)
}
return calibrationAssignments
}
func buildDomainEntityRef(idVal string) *platformclientv2.Domainentityref {
if idVal == "nil" {
return nil
}
return &platformclientv2.Domainentityref{
Id: &idVal,
}
}
func buildAssignSurveys(assignments []interface{}, pp *policyProxy, ctx context.Context) *[]platformclientv2.Surveyassignment {
surveyAssignments := make([]platformclientv2.Surveyassignment, 0)
for _, assignment := range assignments {
assignmentMap, ok := assignment.(map[string]interface{})
if !ok {
continue
}
sendingUser := assignmentMap["sending_user"].(string)
sendingDomain := assignmentMap["sending_domain"].(string)
inviteTimeInterval := assignmentMap["invite_time_interval"].(string)
surveyFormName := assignmentMap["survey_form_name"].(string)
temp := platformclientv2.Surveyassignment{
Flow: buildDomainEntityRef(assignmentMap["flow_id"].(string)),
InviteTimeInterval: &inviteTimeInterval,
SendingUser: &sendingUser,
SendingDomain: &sendingDomain,
}
// If a survey form name is provided, get the context id and build the published survey form reference
if surveyFormName != "" {
form, resp, err := pp.getQualityFormsSurveyByName(ctx, surveyFormName)
if err != nil {
log.Fatalf("Error requesting survey forms %s: %s %v", surveyFormName, err, resp)
} else {
surveyFormReference := platformclientv2.Publishedsurveyformreference{Name: &surveyFormName, ContextId: form.ContextId}
temp.SurveyForm = &surveyFormReference
}
}
surveyAssignments = append(surveyAssignments, temp)
}
return &surveyAssignments
}
func flattenAssignSurveys(assignments *[]platformclientv2.Surveyassignment) []interface{} {
if assignments == nil {
return nil
}
var surveyAssignments []interface{}
for _, assignment := range *assignments {
assignmentMap := make(map[string]interface{}, 0)
if assignment.SurveyForm != nil && assignment.SurveyForm.Name != nil {
assignmentMap["survey_form_name"] = *assignment.SurveyForm.Name
}
if assignment.Flow != nil && assignment.Flow.Id != nil {
assignmentMap["flow_id"] = *assignment.Flow.Id
}
resourcedata.SetMapValueIfNotNil(assignmentMap, "invite_time_interval", assignment.InviteTimeInterval)
resourcedata.SetMapValueIfNotNil(assignmentMap, "sending_user", assignment.SendingUser)
resourcedata.SetMapValueIfNotNil(assignmentMap, "sending_domain", assignment.SendingDomain)
surveyAssignments = append(surveyAssignments, assignmentMap)
}
return surveyAssignments
}
func buildArchiveRetention(archiveRetention []interface{}) *platformclientv2.Archiveretention {
if archiveRetention == nil || len(archiveRetention) <= 0 {
return nil
}
archiveRetentionMap, ok := archiveRetention[0].(map[string]interface{})
if !ok {
return nil
}
days := archiveRetentionMap["days"].(int)
storageMedium := archiveRetentionMap["storage_medium"].(string)
return &platformclientv2.Archiveretention{
Days: &days,
StorageMedium: &storageMedium,
}
}
func flattenArchiveRetention(archiveRetention *platformclientv2.Archiveretention) []interface{} {
if archiveRetention == nil {
return nil
}
archiveRetentionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(archiveRetentionMap, "days", archiveRetention.Days)
resourcedata.SetMapValueIfNotNil(archiveRetentionMap, "storage_medium", archiveRetention.StorageMedium)
return []interface{}{archiveRetentionMap}
}
func buildDeleteRetention(deleteRetention []interface{}) *platformclientv2.Deleteretention {
if deleteRetention == nil || len(deleteRetention) <= 0 {
return nil
}
deleteRetentionMap, ok := deleteRetention[0].(map[string]interface{})
if !ok {
return nil
}
days := deleteRetentionMap["days"].(int)
return &platformclientv2.Deleteretention{
Days: &days,
}
}
func flattenDeleteRetention(deleteRetention *platformclientv2.Deleteretention) []interface{} {
if deleteRetention == nil {
return nil
}
deleteRetentionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(deleteRetentionMap, "days", deleteRetention.Days)
return []interface{}{deleteRetentionMap}
}
func buildRetentionDuration(retentionDuration []interface{}) *platformclientv2.Retentionduration {
if retentionDuration == nil || len(retentionDuration) <= 0 {
return nil
}
retentionDurationMap, ok := retentionDuration[0].(map[string]interface{})
if !ok {
return nil
}
return &platformclientv2.Retentionduration{
ArchiveRetention: buildArchiveRetention(retentionDurationMap["archive_retention"].([]interface{})),
DeleteRetention: buildDeleteRetention(retentionDurationMap["delete_retention"].([]interface{})),
}
}
func flattenRetentionDuration(retentionDuration *platformclientv2.Retentionduration) []interface{} {
if retentionDuration == nil {
return nil
}
retentionDurationMap := make(map[string]interface{})
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(retentionDurationMap, "archive_retention", retentionDuration.ArchiveRetention, flattenArchiveRetention)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(retentionDurationMap, "delete_retention", retentionDuration.DeleteRetention, flattenDeleteRetention)
return []interface{}{retentionDurationMap}
}
func buildInitiateScreenRecording(initiateScreenRecording []interface{}) *platformclientv2.Initiatescreenrecording {
if initiateScreenRecording == nil || len(initiateScreenRecording) <= 0 {
return nil
}
initiateScreenRecordingMap, ok := initiateScreenRecording[0].(map[string]interface{})
if !ok {
return nil
}
recordACW := initiateScreenRecordingMap["record_acw"].(bool)
return &platformclientv2.Initiatescreenrecording{
RecordACW: &recordACW,
ArchiveRetention: buildArchiveRetention(initiateScreenRecordingMap["archive_retention"].([]interface{})),
DeleteRetention: buildDeleteRetention(initiateScreenRecordingMap["delete_retention"].([]interface{})),
}
}
func flattenInitiateScreenRecording(recording *platformclientv2.Initiatescreenrecording) []interface{} {
if recording == nil {
return nil
}
recordingMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(recordingMap, "record_acw", recording.RecordACW)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(recordingMap, "archive_retention", recording.ArchiveRetention, flattenArchiveRetention)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(recordingMap, "delete_retention", recording.DeleteRetention, flattenDeleteRetention)
return []interface{}{recordingMap}
}
func buildMediaTranscriptions(transcriptions []interface{}) *[]platformclientv2.Mediatranscription {
mediaTranscriptions := make([]platformclientv2.Mediatranscription, 0)
for _, transcription := range transcriptions {
transcriptionMap, ok := transcription.(map[string]interface{})
if !ok {
continue
}
displayName := transcriptionMap["display_name"].(string)
transcriptionProvider := transcriptionMap["transcription_provider"].(string)
integrationId := transcriptionMap["integration_id"].(string)
mediaTranscriptions = append(mediaTranscriptions, platformclientv2.Mediatranscription{
DisplayName: &displayName,
TranscriptionProvider: &transcriptionProvider,
IntegrationId: &integrationId,
})
}
return &mediaTranscriptions
}
func flattenMediaTranscriptions(transcriptions *[]platformclientv2.Mediatranscription) []interface{} {
if transcriptions == nil {
return nil
}
mediaTranscriptions := make([]interface{}, 0)
for _, transcription := range *transcriptions {
transcriptionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(transcriptionMap, "display_name", transcription.DisplayName)
resourcedata.SetMapValueIfNotNil(transcriptionMap, "transcription_provider", transcription.TranscriptionProvider)
resourcedata.SetMapValueIfNotNil(transcriptionMap, "integration_id", transcription.IntegrationId)
mediaTranscriptions = append(mediaTranscriptions, transcriptionMap)
}
return mediaTranscriptions
}
func buildIntegrationExport(integrationExport []interface{}) *platformclientv2.Integrationexport {
if integrationExport == nil || len(integrationExport) <= 0 {
return nil
}
integrationExportMap, ok := integrationExport[0].(map[string]interface{})
if !ok {
return nil
}
shouldExportScreenRecordings := integrationExportMap["should_export_screen_recordings"].(bool)
return &platformclientv2.Integrationexport{
Integration: buildDomainEntityRef(integrationExportMap["integration_id"].(string)),
ShouldExportScreenRecordings: &shouldExportScreenRecordings,
}
}
func flattenIntegrationExport(integrationExport *platformclientv2.Integrationexport) []interface{} {
if integrationExport == nil {
return nil
}
integrationExportMap := make(map[string]interface{})
if integrationExport.Integration != nil {
integrationExportMap["integration_id"] = *integrationExport.Integration.Id
}
resourcedata.SetMapValueIfNotNil(integrationExportMap, "should_export_screen_recordings", integrationExport.ShouldExportScreenRecordings)
return []interface{}{integrationExportMap}
}
func buildPolicyActionsFromMediaPolicy(actions []interface{}, pp *policyProxy, ctx context.Context) *platformclientv2.Policyactions {
if actions == nil || len(actions) <= 0 {
return nil
}
actionsMap, ok := actions[0].(map[string]interface{})
if !ok {
return nil
}
retainRecording := actionsMap["retain_recording"].(bool)
deleteRecording := actionsMap["delete_recording"].(bool)
alwaysDelete := actionsMap["always_delete"].(bool)
assignMeteredAssignmentByAgent := buildAssignMeteredAssignmentByAgent(actionsMap["assign_metered_assignment_by_agent"].([]interface{}), pp, ctx)
assignMeteredEvaluations := buildAssignMeteredEvaluations(actionsMap["assign_metered_evaluations"].([]interface{}), pp, ctx)
return &platformclientv2.Policyactions{
RetainRecording: &retainRecording,
DeleteRecording: &deleteRecording,
AlwaysDelete: &alwaysDelete,
AssignEvaluations: buildEvaluationAssignments(actionsMap["assign_evaluations"].([]interface{}), pp, ctx),
AssignMeteredEvaluations: assignMeteredEvaluations,
AssignMeteredAssignmentByAgent: assignMeteredAssignmentByAgent,
AssignCalibrations: buildAssignCalibrations(actionsMap["assign_calibrations"].([]interface{}), pp, ctx),
AssignSurveys: buildAssignSurveys(actionsMap["assign_surveys"].([]interface{}), pp, ctx),
RetentionDuration: buildRetentionDuration(actionsMap["retention_duration"].([]interface{})),
InitiateScreenRecording: buildInitiateScreenRecording(actionsMap["initiate_screen_recording"].([]interface{})),
MediaTranscriptions: buildMediaTranscriptions(actionsMap["media_transcriptions"].([]interface{})),
IntegrationExport: buildIntegrationExport(actionsMap["integration_export"].([]interface{})),
}
}
func flattenPolicyActions(actions *platformclientv2.Policyactions, pp *policyProxy, ctx context.Context) []interface{} {
if actions == nil || reflect.DeepEqual(platformclientv2.Policyactions{}, *actions) {
return nil
}
actionsMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(actionsMap, "retain_recording", actions.RetainRecording)
resourcedata.SetMapValueIfNotNil(actionsMap, "delete_recording", actions.DeleteRecording)
resourcedata.SetMapValueIfNotNil(actionsMap, "always_delete", actions.AlwaysDelete)
if actions.AssignEvaluations != nil {
actionsMap["assign_evaluations"] = flattenEvaluationAssignments(actions.AssignEvaluations, pp, ctx)
}
if actions.AssignMeteredEvaluations != nil {
actionsMap["assign_metered_evaluations"] = flattenAssignMeteredEvaluations(actions.AssignMeteredEvaluations, pp, ctx)
}
if actions.AssignMeteredAssignmentByAgent != nil {
actionsMap["assign_metered_assignment_by_agent"] = flattenAssignMeteredAssignmentByAgent(actions.AssignMeteredAssignmentByAgent, pp, ctx)
}
if actions.AssignCalibrations != nil {
actionsMap["assign_calibrations"] = flattenAssignCalibrations(actions.AssignCalibrations, pp, ctx)
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(actionsMap, "assign_surveys", actions.AssignSurveys, flattenAssignSurveys)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(actionsMap, "retention_duration", actions.RetentionDuration, flattenRetentionDuration)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(actionsMap, "initiate_screen_recording", actions.InitiateScreenRecording, flattenInitiateScreenRecording)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(actionsMap, "media_transcriptions", actions.MediaTranscriptions, flattenMediaTranscriptions)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(actionsMap, "integration_export", actions.IntegrationExport, flattenIntegrationExport)
return []interface{}{actionsMap}
}
func buildTimeSlots(slots []interface{}) *[]platformclientv2.Timeslot {
timeSlots := make([]platformclientv2.Timeslot, 0)
for _, slot := range slots {
slotMap, ok := slot.(map[string]interface{})
if !ok {
continue
}
startTime := slotMap["start_time"].(string)
stopTime := slotMap["stop_time"].(string)
day := slotMap["day"].(int)
timeSlots = append(timeSlots, platformclientv2.Timeslot{
StartTime: &startTime,
StopTime: &stopTime,
Day: &day,
})
}
return &timeSlots
}
func flattenTimeSlots(slots *[]platformclientv2.Timeslot) []interface{} {
if slots == nil {
return nil
}
slotList := make([]interface{}, 0)
for _, slot := range *slots {
slotMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(slotMap, "start_time", slot.StartTime)
resourcedata.SetMapValueIfNotNil(slotMap, "stop_time", slot.StopTime)
resourcedata.SetMapValueIfNotNil(slotMap, "day", slot.Day)
slotList = append(slotList, slotMap)
}
return slotList
}
func buildTimeAllowed(timeAllowed []interface{}) *platformclientv2.Timeallowed {
if timeAllowed == nil || len(timeAllowed) <= 0 {
return nil
}
timeAllowedMap, ok := timeAllowed[0].(map[string]interface{})
if !ok {
return nil
}
timeZoneId := timeAllowedMap["time_zone_id"].(string)
empty := timeAllowedMap["empty"].(bool)
return &platformclientv2.Timeallowed{
TimeSlots: buildTimeSlots(timeAllowedMap["time_slots"].([]interface{})),
TimeZoneId: &timeZoneId,
Empty: &empty,
}
}
func flattenTimeAllowed(timeAllowed *platformclientv2.Timeallowed) []interface{} {
if timeAllowed == nil {
return nil
}
timeAllowedMap := make(map[string]interface{})
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(timeAllowedMap, "time_slots", timeAllowed.TimeSlots, flattenTimeSlots)
resourcedata.SetMapValueIfNotNil(timeAllowedMap, "time_zone_id", timeAllowed.TimeZoneId)
resourcedata.SetMapValueIfNotNil(timeAllowedMap, "empty", timeAllowed.Empty)
return []interface{}{timeAllowedMap}
}
func buildDurationCondition(durationCondition []interface{}) *platformclientv2.Durationcondition {
if durationCondition == nil || len(durationCondition) <= 0 {
return nil
}
durationConditionMap, ok := durationCondition[0].(map[string]interface{})
if !ok {
return nil
}
durationTarget := durationConditionMap["duration_target"].(string)
durationOperator := durationConditionMap["duration_operator"].(string)
durationRange := durationConditionMap["duration_range"].(string)
durationMode := durationConditionMap["duration_mode"].(string)
return &platformclientv2.Durationcondition{
DurationTarget: &durationTarget,
DurationOperator: &durationOperator,
DurationRange: &durationRange,
DurationMode: &durationMode,
}
}
func flattenDurationCondition(durationCondition *platformclientv2.Durationcondition) []interface{} {
if durationCondition == nil {
return nil
}
durationConditionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(durationConditionMap, "duration_target", durationCondition.DurationTarget)
resourcedata.SetMapValueIfNotNil(durationConditionMap, "duration_operator", durationCondition.DurationOperator)
resourcedata.SetMapValueIfNotNil(durationConditionMap, "duration_range", durationCondition.DurationRange)
resourcedata.SetMapValueIfNotNil(durationConditionMap, "duration_mode", durationCondition.DurationMode)
return []interface{}{durationConditionMap}
}
func buildCallMediaPolicyConditions(callMediaPolicyConditions []interface{}) *platformclientv2.Callmediapolicyconditions {
if callMediaPolicyConditions == nil || len(callMediaPolicyConditions) <= 0 {
return nil
}
conditionsMap, ok := callMediaPolicyConditions[0].(map[string]interface{})
if !ok {
return nil
}
directions := make([]string, 0)
for _, v := range conditionsMap["directions"].([]interface{}) {
direction := fmt.Sprintf("%v", v)
directions = append(directions, direction)
}
dateRanges := make([]string, 0)
for _, v := range conditionsMap["date_ranges"].([]interface{}) {
dateRange := fmt.Sprintf("%v", v)
dateRanges = append(dateRanges, dateRange)
}
forUserIds := conditionsMap["for_user_ids"].([]interface{})
idStrings := make([]string, 0)
for _, id := range forUserIds {
idStrings = append(idStrings, fmt.Sprintf("%v", id))
}
forUsers := make([]platformclientv2.User, 0)
for _, id := range idStrings {
userId := id
forUsers = append(forUsers, platformclientv2.User{Id: &userId})
}
wrapupCodeIds := conditionsMap["wrapup_code_ids"].([]interface{})
wrapupCodeIdStrings := make([]string, 0)
for _, id := range wrapupCodeIds {
wrapupCodeIdStrings = append(wrapupCodeIdStrings, fmt.Sprintf("%v", id))
}
wrapupCodes := make([]platformclientv2.Wrapupcode, 0)
for _, id := range wrapupCodeIdStrings {
wrapupId := id
wrapupCodes = append(wrapupCodes, platformclientv2.Wrapupcode{Id: &wrapupId})
}
languageIds := conditionsMap["language_ids"].([]interface{})
languageIdStrings := make([]string, 0)
for _, id := range languageIds {
languageIdStrings = append(languageIdStrings, fmt.Sprintf("%v", id))
}
languages := make([]platformclientv2.Language, 0)
for _, id := range languageIdStrings {
languageId := id
languages = append(languages, platformclientv2.Language{Id: &languageId})
}
forQueueIds := conditionsMap["for_queue_ids"].([]interface{})
queueIdStrings := make([]string, 0)
for _, id := range forQueueIds {
queueIdStrings = append(queueIdStrings, fmt.Sprintf("%v", id))
}
forQueues := make([]platformclientv2.Queue, 0)
for _, id := range queueIdStrings {
queueId := id
forQueues = append(forQueues, platformclientv2.Queue{Id: &queueId})
}
return &platformclientv2.Callmediapolicyconditions{
ForUsers: &forUsers,
DateRanges: &dateRanges,
ForQueues: &forQueues,
WrapupCodes: &wrapupCodes,
Languages: &languages,
TimeAllowed: buildTimeAllowed(conditionsMap["time_allowed"].([]interface{})),
Directions: &directions,
Duration: buildDurationCondition(conditionsMap["duration"].([]interface{})),
}
}
func flattenCallMediaPolicyConditions(conditions *platformclientv2.Callmediapolicyconditions) []interface{} {
if conditions == nil {
return nil
}
conditionsMap := make(map[string]interface{})
if conditions.ForUsers != nil {
userIds := make([]string, 0)
for _, user := range *conditions.ForUsers {
userIds = append(userIds, *user.Id)
}
conditionsMap["for_user_ids"] = userIds
}
resourcedata.SetMapValueIfNotNil(conditionsMap, "date_ranges", conditions.DateRanges)
resourcedata.SetMapValueIfNotNil(conditionsMap, "directions", conditions.Directions)
if conditions.ForQueues != nil {
queueIds := make([]string, 0)
for _, queue := range *conditions.ForQueues {
queueIds = append(queueIds, *queue.Id)
}
conditionsMap["for_queue_ids"] = queueIds
}
if conditions.WrapupCodes != nil {
wrapupCodeIds := make([]string, 0)
for _, code := range *conditions.WrapupCodes {
wrapupCodeIds = append(wrapupCodeIds, *code.Id)
}
conditionsMap["wrapup_code_ids"] = wrapupCodeIds
}
if conditions.Languages != nil {
languageIds := make([]string, 0)
for _, code := range *conditions.Languages {
languageIds = append(languageIds, *code.Id)
}
conditionsMap["language_ids"] = languageIds
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionsMap, "time_allowed", conditions.TimeAllowed, flattenTimeAllowed)
return []interface{}{conditionsMap}
}
func buildChatMediaPolicyConditions(chatMediaPolicyConditions []interface{}) *platformclientv2.Chatmediapolicyconditions {
if chatMediaPolicyConditions == nil || len(chatMediaPolicyConditions) <= 0 {
return nil
}
conditionsMap, ok := chatMediaPolicyConditions[0].(map[string]interface{})
if !ok {
return nil
}
dateRanges := make([]string, 0)
for _, v := range conditionsMap["date_ranges"].([]interface{}) {
dateRange := fmt.Sprintf("%v", v)
dateRanges = append(dateRanges, dateRange)
}
forUserIds := conditionsMap["for_user_ids"].([]interface{})
idStrings := make([]string, 0)
for _, id := range forUserIds {
idStrings = append(idStrings, fmt.Sprintf("%v", id))
}
forUsers := make([]platformclientv2.User, 0)
for _, id := range idStrings {
userId := id
forUsers = append(forUsers, platformclientv2.User{Id: &userId})
}
wrapupCodeIds := conditionsMap["wrapup_code_ids"].([]interface{})
wrapupCodeIdStrings := make([]string, 0)
for _, id := range wrapupCodeIds {
wrapupCodeIdStrings = append(wrapupCodeIdStrings, fmt.Sprintf("%v", id))
}
wrapupCodes := make([]platformclientv2.Wrapupcode, 0)
for _, id := range wrapupCodeIdStrings {
wrapupId := id
wrapupCodes = append(wrapupCodes, platformclientv2.Wrapupcode{Id: &wrapupId})
}
languageIds := conditionsMap["language_ids"].([]interface{})
languageIdStrings := make([]string, 0)
for _, id := range languageIds {
languageIdStrings = append(languageIdStrings, fmt.Sprintf("%v", id))
}
languages := make([]platformclientv2.Language, 0)
for _, id := range languageIdStrings {
languageId := id
languages = append(languages, platformclientv2.Language{Id: &languageId})
}
forQueueIds := conditionsMap["for_queue_ids"].([]interface{})
queueIdStrings := make([]string, 0)
for _, id := range forQueueIds {
queueIdStrings = append(queueIdStrings, fmt.Sprintf("%v", id))
}
forQueues := make([]platformclientv2.Queue, 0)
for _, id := range queueIdStrings {
queueId := id
forQueues = append(forQueues, platformclientv2.Queue{Id: &queueId})
}
return &platformclientv2.Chatmediapolicyconditions{
ForUsers: &forUsers,
DateRanges: &dateRanges,
ForQueues: &forQueues,
WrapupCodes: &wrapupCodes,
Languages: &languages,
TimeAllowed: buildTimeAllowed(conditionsMap["time_allowed"].([]interface{})),
Duration: buildDurationCondition(conditionsMap["duration"].([]interface{})),
}
}
func flattenChatMediaPolicyConditions(conditions *platformclientv2.Chatmediapolicyconditions) []interface{} {
if conditions == nil {
return nil
}
conditionsMap := make(map[string]interface{})
if conditions.ForUsers != nil {
userIds := make([]string, 0)
for _, user := range *conditions.ForUsers {
userIds = append(userIds, *user.Id)
}
conditionsMap["for_user_ids"] = userIds
}
resourcedata.SetMapValueIfNotNil(conditionsMap, "date_ranges", conditions.DateRanges)
if conditions.ForQueues != nil {
queueIds := make([]string, 0)
for _, queue := range *conditions.ForQueues {
queueIds = append(queueIds, *queue.Id)
}
conditionsMap["for_queue_ids"] = queueIds
}
if conditions.WrapupCodes != nil {
wrapupCodeIds := make([]string, 0)
for _, code := range *conditions.WrapupCodes {
wrapupCodeIds = append(wrapupCodeIds, *code.Id)
}
conditionsMap["wrapup_code_ids"] = wrapupCodeIds
}
if conditions.Languages != nil {
languageIds := make([]string, 0)
for _, code := range *conditions.Languages {
languageIds = append(languageIds, *code.Id)
}
conditionsMap["language_ids"] = languageIds
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionsMap, "time_allowed", conditions.TimeAllowed, flattenTimeAllowed)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionsMap, "duration", conditions.Duration, flattenDurationCondition)
return []interface{}{conditionsMap}
}
func buildEmailMediaPolicyConditions(emailMediaPolicyConditions []interface{}) *platformclientv2.Emailmediapolicyconditions {
if emailMediaPolicyConditions == nil || len(emailMediaPolicyConditions) <= 0 {
return nil
}
conditionsMap, ok := emailMediaPolicyConditions[0].(map[string]interface{})
if !ok {
return nil
}
dateRanges := make([]string, 0)
for _, v := range conditionsMap["date_ranges"].([]interface{}) {
dateRange := fmt.Sprintf("%v", v)
dateRanges = append(dateRanges, dateRange)
}
forUserIds := conditionsMap["for_user_ids"].([]interface{})
idStrings := make([]string, 0)
for _, id := range forUserIds {
idStrings = append(idStrings, fmt.Sprintf("%v", id))
}
forUsers := make([]platformclientv2.User, 0)
for _, id := range idStrings {
userId := id
forUsers = append(forUsers, platformclientv2.User{Id: &userId})
}
wrapupCodeIds := conditionsMap["wrapup_code_ids"].([]interface{})
wrapupCodeIdStrings := make([]string, 0)
for _, id := range wrapupCodeIds {
wrapupCodeIdStrings = append(wrapupCodeIdStrings, fmt.Sprintf("%v", id))
}
wrapupCodes := make([]platformclientv2.Wrapupcode, 0)
for _, id := range wrapupCodeIdStrings {
wrapupId := id
wrapupCodes = append(wrapupCodes, platformclientv2.Wrapupcode{Id: &wrapupId})
}
languageIds := conditionsMap["language_ids"].([]interface{})
languageIdStrings := make([]string, 0)
for _, id := range languageIds {
languageIdStrings = append(languageIdStrings, fmt.Sprintf("%v", id))
}
languages := make([]platformclientv2.Language, 0)
for _, id := range languageIdStrings {
languageId := id
languages = append(languages, platformclientv2.Language{Id: &languageId})
}
forQueueIds := conditionsMap["for_queue_ids"].([]interface{})
queueIdStrings := make([]string, 0)
for _, id := range forQueueIds {
queueIdStrings = append(queueIdStrings, fmt.Sprintf("%v", id))
}
forQueues := make([]platformclientv2.Queue, 0)
for _, id := range queueIdStrings {
queueId := id
forQueues = append(forQueues, platformclientv2.Queue{Id: &queueId})
}
return &platformclientv2.Emailmediapolicyconditions{
ForUsers: &forUsers,
DateRanges: &dateRanges,
ForQueues: &forQueues,
WrapupCodes: &wrapupCodes,
Languages: &languages,
TimeAllowed: buildTimeAllowed(conditionsMap["time_allowed"].([]interface{})),
}
}
func flattenEmailMediaPolicyConditions(conditions *platformclientv2.Emailmediapolicyconditions) []interface{} {
if conditions == nil {
return nil
}
conditionsMap := make(map[string]interface{})
if conditions.ForUsers != nil {
userIds := make([]string, 0)
for _, user := range *conditions.ForUsers {
userIds = append(userIds, *user.Id)
}
conditionsMap["for_user_ids"] = userIds
}
resourcedata.SetMapValueIfNotNil(conditionsMap, "date_ranges", conditions.DateRanges)
if conditions.ForQueues != nil {
queueIds := make([]string, 0)
for _, queue := range *conditions.ForQueues {
queueIds = append(queueIds, *queue.Id)
}
conditionsMap["for_queue_ids"] = queueIds
}
if conditions.WrapupCodes != nil {
wrapupCodeIds := make([]string, 0)
for _, code := range *conditions.WrapupCodes {
wrapupCodeIds = append(wrapupCodeIds, *code.Id)
}
conditionsMap["wrapup_code_ids"] = wrapupCodeIds
}
if conditions.Languages != nil {
languageIds := make([]string, 0)
for _, code := range *conditions.Languages {
languageIds = append(languageIds, *code.Id)
}
conditionsMap["language_ids"] = languageIds
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionsMap, "time_allowed", conditions.TimeAllowed, flattenTimeAllowed)
return []interface{}{conditionsMap}
}
func buildMessageMediaPolicyConditions(messageMediaPolicyConditions []interface{}) *platformclientv2.Messagemediapolicyconditions {
if messageMediaPolicyConditions == nil || len(messageMediaPolicyConditions) <= 0 {
return nil
}
conditionsMap, ok := messageMediaPolicyConditions[0].(map[string]interface{})
if !ok {
return nil
}
dateRanges := make([]string, 0)
for _, v := range conditionsMap["date_ranges"].([]interface{}) {
dateRange := fmt.Sprintf("%v", v)
dateRanges = append(dateRanges, dateRange)
}
forUserIds := conditionsMap["for_user_ids"].([]interface{})
idStrings := make([]string, 0)
for _, id := range forUserIds {
idStrings = append(idStrings, fmt.Sprintf("%v", id))
}
forUsers := make([]platformclientv2.User, 0)
for _, id := range idStrings {
userId := id
forUsers = append(forUsers, platformclientv2.User{Id: &userId})
}
wrapupCodeIds := conditionsMap["wrapup_code_ids"].([]interface{})
wrapupCodeIdStrings := make([]string, 0)
for _, id := range wrapupCodeIds {
wrapupCodeIdStrings = append(wrapupCodeIdStrings, fmt.Sprintf("%v", id))
}
wrapupCodes := make([]platformclientv2.Wrapupcode, 0)
for _, id := range wrapupCodeIdStrings {
wrapupId := id
wrapupCodes = append(wrapupCodes, platformclientv2.Wrapupcode{Id: &wrapupId})
}
languageIds := conditionsMap["language_ids"].([]interface{})
languageIdStrings := make([]string, 0)
for _, id := range languageIds {
languageIdStrings = append(languageIdStrings, fmt.Sprintf("%v", id))
}
languages := make([]platformclientv2.Language, 0)
for _, id := range languageIdStrings {
languageId := id
languages = append(languages, platformclientv2.Language{Id: &languageId})
}
forQueueIds := conditionsMap["for_queue_ids"].([]interface{})
queueIdStrings := make([]string, 0)
for _, id := range forQueueIds {
queueIdStrings = append(queueIdStrings, fmt.Sprintf("%v", id))
}
forQueues := make([]platformclientv2.Queue, 0)
for _, id := range queueIdStrings {
queueId := id
forQueues = append(forQueues, platformclientv2.Queue{Id: &queueId})
}
return &platformclientv2.Messagemediapolicyconditions{
ForUsers: &forUsers,
DateRanges: &dateRanges,
ForQueues: &forQueues,
WrapupCodes: &wrapupCodes,
Languages: &languages,
TimeAllowed: buildTimeAllowed(conditionsMap["time_allowed"].([]interface{})),
}
}
func flattenMessageMediaPolicyConditions(conditions *platformclientv2.Messagemediapolicyconditions) []interface{} {
if conditions == nil {
return nil
}
conditionsMap := make(map[string]interface{})
if conditions.ForUsers != nil {
userIds := make([]string, 0)
for _, user := range *conditions.ForUsers {
userIds = append(userIds, *user.Id)
}
conditionsMap["for_user_ids"] = userIds
}
resourcedata.SetMapValueIfNotNil(conditionsMap, "date_ranges", conditions.DateRanges)
if conditions.ForQueues != nil {
queueIds := make([]string, 0)
for _, queue := range *conditions.ForQueues {
queueIds = append(queueIds, *queue.Id)
}
conditionsMap["for_queue_ids"] = queueIds
}
if conditions.WrapupCodes != nil {
wrapupCodeIds := make([]string, 0)
for _, code := range *conditions.WrapupCodes {
wrapupCodeIds = append(wrapupCodeIds, *code.Id)
}
conditionsMap["wrapup_code_ids"] = wrapupCodeIds
}
if conditions.Languages != nil {
languageIds := make([]string, 0)
for _, code := range *conditions.Languages {
languageIds = append(languageIds, *code.Id)
}
conditionsMap["language_ids"] = languageIds
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionsMap, "time_allowed", conditions.TimeAllowed, flattenTimeAllowed)
return []interface{}{conditionsMap}
}
func buildCallMediaPolicy(callMediaPolicy []interface{}, pp *policyProxy, ctx context.Context) *platformclientv2.Callmediapolicy {
if callMediaPolicy == nil || len(callMediaPolicy) <= 0 {
return nil
}
policyMap, ok := callMediaPolicy[0].(map[string]interface{})
if !ok {
return nil
}
actions := buildPolicyActionsFromMediaPolicy(policyMap["actions"].([]interface{}), pp, ctx)
return &platformclientv2.Callmediapolicy{
Actions: actions,
Conditions: buildCallMediaPolicyConditions(policyMap["conditions"].([]interface{})),
}
}
func flattenCallMediaPolicy(chatMediaPolicy *platformclientv2.Callmediapolicy, pp *policyProxy, ctx context.Context) []interface{} {
if chatMediaPolicy == nil {
return nil
}
chatMediaPolicyMap := make(map[string]interface{})
if chatMediaPolicy.Actions != nil {
chatMediaPolicyMap["actions"] = flattenPolicyActions(chatMediaPolicy.Actions, pp, ctx)
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(chatMediaPolicyMap, "conditions", chatMediaPolicy.Conditions, flattenCallMediaPolicyConditions)
return []interface{}{chatMediaPolicyMap}
}
func buildChatMediaPolicy(chatMediaPolicy []interface{}, pp *policyProxy, ctx context.Context) *platformclientv2.Chatmediapolicy {
if chatMediaPolicy == nil || len(chatMediaPolicy) <= 0 {
return nil
}
policyMap, ok := chatMediaPolicy[0].(map[string]interface{})
if !ok {
return nil
}
actions := buildPolicyActionsFromMediaPolicy(policyMap["actions"].([]interface{}), pp, ctx)
return &platformclientv2.Chatmediapolicy{
Actions: actions,
Conditions: buildChatMediaPolicyConditions(policyMap["conditions"].([]interface{})),
}
}
func flattenChatMediaPolicy(chatMediaPolicy *platformclientv2.Chatmediapolicy, pp *policyProxy, ctx context.Context) []interface{} {
if chatMediaPolicy == nil {
return nil
}
chatMediaPolicyMap := make(map[string]interface{})
if chatMediaPolicy.Actions != nil {
chatMediaPolicyMap["actions"] = flattenPolicyActions(chatMediaPolicy.Actions, pp, ctx)
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(chatMediaPolicyMap, "conditions", chatMediaPolicy.Conditions, flattenChatMediaPolicyConditions)
return []interface{}{chatMediaPolicyMap}
}
func buildEmailMediaPolicy(emailMediaPolicy []interface{}, pp *policyProxy, ctx context.Context) *platformclientv2.Emailmediapolicy {
if emailMediaPolicy == nil || len(emailMediaPolicy) <= 0 {
return nil
}
policyMap, ok := emailMediaPolicy[0].(map[string]interface{})
if !ok {
return nil
}
actions := buildPolicyActionsFromMediaPolicy(policyMap["actions"].([]interface{}), pp, ctx)
return &platformclientv2.Emailmediapolicy{
Actions: actions,
Conditions: buildEmailMediaPolicyConditions(policyMap["conditions"].([]interface{})),
}
}
func flattenEmailMediaPolicy(emailMediaPolicy *platformclientv2.Emailmediapolicy, pp *policyProxy, ctx context.Context) []interface{} {
if emailMediaPolicy == nil {
return nil
}
emailMediaPolicyMap := make(map[string]interface{})
if emailMediaPolicy.Actions != nil {
emailMediaPolicyMap["actions"] = flattenPolicyActions(emailMediaPolicy.Actions, pp, ctx)
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(emailMediaPolicyMap, "conditions", emailMediaPolicy.Conditions, flattenEmailMediaPolicyConditions)
return []interface{}{emailMediaPolicyMap}
}
func buildMessageMediaPolicy(messageMediaPolicy []interface{}, pp *policyProxy, ctx context.Context) *platformclientv2.Messagemediapolicy {
if messageMediaPolicy == nil || len(messageMediaPolicy) <= 0 {
return nil
}
policyMap, ok := messageMediaPolicy[0].(map[string]interface{})
if !ok {
return nil
}
actions := buildPolicyActionsFromMediaPolicy(policyMap["actions"].([]interface{}), pp, ctx)
return &platformclientv2.Messagemediapolicy{
Actions: actions,
Conditions: buildMessageMediaPolicyConditions(policyMap["conditions"].([]interface{})),
}
}
func flattenMessageMediaPolicy(messageMediaPolicy *platformclientv2.Messagemediapolicy, pp *policyProxy, ctx context.Context) []interface{} {
if messageMediaPolicy == nil {
return nil
}
messageMediaPolicyMap := make(map[string]interface{})
if messageMediaPolicy.Actions != nil {
messageMediaPolicyMap["actions"] = flattenPolicyActions(messageMediaPolicy.Actions, pp, ctx)
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(messageMediaPolicyMap, "conditions", messageMediaPolicy.Conditions, flattenMessageMediaPolicyConditions)
return []interface{}{messageMediaPolicyMap}
}
func buildMediaPolicies(d *schema.ResourceData, pp *policyProxy, ctx context.Context) *platformclientv2.Mediapolicies {
sdkMediaPolicies := platformclientv2.Mediapolicies{}
if mediaPolicies, ok := d.Get("media_policies").([]interface{}); ok && len(mediaPolicies) > 0 {
mediaPoliciesMap, ok := mediaPolicies[0].(map[string]interface{})
if !ok {
return nil
}
if callPolicy := mediaPoliciesMap["call_policy"]; callPolicy != nil {
sdkMediaPolicies.CallPolicy = buildCallMediaPolicy(callPolicy.([]interface{}), pp, ctx)
}
if chatPolicy := mediaPoliciesMap["chat_policy"]; chatPolicy != nil {
sdkMediaPolicies.ChatPolicy = buildChatMediaPolicy(chatPolicy.([]interface{}), pp, ctx)
}
if emailPolicy := mediaPoliciesMap["email_policy"]; emailPolicy != nil {
sdkMediaPolicies.EmailPolicy = buildEmailMediaPolicy(emailPolicy.([]interface{}), pp, ctx)
}
if messagePolicy := mediaPoliciesMap["message_policy"]; messagePolicy != nil {
sdkMediaPolicies.MessagePolicy = buildMessageMediaPolicy(messagePolicy.([]interface{}), pp, ctx)
}
}
return &sdkMediaPolicies
}
func flattenMediaPolicies(mediaPolicies *platformclientv2.Mediapolicies, pp *policyProxy, ctx context.Context) []interface{} {
if mediaPolicies == nil {
return nil
}
mediaPoliciesMap := make(map[string]interface{})
if mediaPolicies.CallPolicy != nil {
mediaPoliciesMap["call_policy"] = flattenCallMediaPolicy(mediaPolicies.CallPolicy, pp, ctx)
}
if mediaPolicies.ChatPolicy != nil {
mediaPoliciesMap["chat_policy"] = flattenChatMediaPolicy(mediaPolicies.ChatPolicy, pp, ctx)
}
if mediaPolicies.EmailPolicy != nil {
mediaPoliciesMap["email_policy"] = flattenEmailMediaPolicy(mediaPolicies.EmailPolicy, pp, ctx)
}
if mediaPolicies.MessagePolicy != nil {
mediaPoliciesMap["message_policy"] = flattenMessageMediaPolicy(mediaPolicies.MessagePolicy, pp, ctx)
}
return []interface{}{mediaPoliciesMap}
}
func buildConditions(d *schema.ResourceData) *platformclientv2.Policyconditions {
if conditions, ok := d.Get("conditions").([]interface{}); ok && len(conditions) > 0 {
conditionsMap, ok := conditions[0].(map[string]interface{})
if !ok {
return nil
}
directions := make([]string, 0)
for _, v := range conditionsMap["directions"].([]interface{}) {
direction := fmt.Sprintf("%v", v)
directions = append(directions, direction)
}
dateRanges := make([]string, 0)
for _, v := range conditionsMap["date_ranges"].([]interface{}) {
dateRange := fmt.Sprintf("%v", v)
dateRanges = append(dateRanges, dateRange)
}
mediaTypes := make([]string, 0)
for _, v := range conditionsMap["media_types"].([]interface{}) {
mediaType := fmt.Sprintf("%v", v)
mediaTypes = append(mediaTypes, mediaType)
}
forUserIds := conditionsMap["for_user_ids"].([]interface{})
idStrings := make([]string, 0)
for _, id := range forUserIds {
idStrings = append(idStrings, fmt.Sprintf("%v", id))
}
forUsers := make([]platformclientv2.User, 0)
for _, id := range idStrings {
userId := id
forUsers = append(forUsers, platformclientv2.User{Id: &userId})
}
wrapupCodeIds := conditionsMap["wrapup_code_ids"].([]interface{})
wrapupCodeIdStrings := make([]string, 0)
for _, id := range wrapupCodeIds {
wrapupCodeIdStrings = append(wrapupCodeIdStrings, fmt.Sprintf("%v", id))
}
wrapupCodes := make([]platformclientv2.Wrapupcode, 0)
for _, id := range wrapupCodeIdStrings {
wrapupId := id
wrapupCodes = append(wrapupCodes, platformclientv2.Wrapupcode{Id: &wrapupId})
}
forQueueIds := conditionsMap["for_queue_ids"].([]interface{})
queueIdStrings := make([]string, 0)
for _, id := range forQueueIds {
queueIdStrings = append(queueIdStrings, fmt.Sprintf("%v", id))
}
forQueues := make([]platformclientv2.Queue, 0)
for _, id := range queueIdStrings {
queueId := id
forQueues = append(forQueues, platformclientv2.Queue{Id: &queueId})
}
return &platformclientv2.Policyconditions{
ForUsers: &forUsers,
Directions: &directions,
DateRanges: &dateRanges,
MediaTypes: &mediaTypes,
ForQueues: &forQueues,
Duration: buildDurationCondition(conditionsMap["duration"].([]interface{})),
WrapupCodes: &wrapupCodes,
TimeAllowed: buildTimeAllowed(conditionsMap["time_allowed"].([]interface{})),
}
}
return nil
}
func flattenConditions(conditions *platformclientv2.Policyconditions) []interface{} {
if conditions == nil || reflect.DeepEqual(platformclientv2.Policyconditions{}, *conditions) {
return nil
}
conditionsMap := make(map[string]interface{})
if conditions.ForUsers != nil {
userIds := make([]string, 0)
for _, user := range *conditions.ForUsers {
userIds = append(userIds, *user.Id)
}
conditionsMap["for_user_ids"] = userIds
}
resourcedata.SetMapValueIfNotNil(conditionsMap, "directions", conditions.Directions)
resourcedata.SetMapValueIfNotNil(conditionsMap, "date_ranges", conditions.DateRanges)
resourcedata.SetMapValueIfNotNil(conditionsMap, "media_types", conditions.MediaTypes)
if conditions.ForQueues != nil {
queueIds := make([]string, 0)
for _, queue := range *conditions.ForQueues {
queueIds = append(queueIds, *queue.Id)
}
conditionsMap["for_queue_ids"] = queueIds
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionsMap, "duration", conditions.Duration, flattenDurationCondition)
if conditions.WrapupCodes != nil {
wrapupCodeIds := make([]string, 0)
for _, code := range *conditions.WrapupCodes {
wrapupCodeIds = append(wrapupCodeIds, *code.Id)
}
conditionsMap["wrapup_code_ids"] = wrapupCodeIds
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(conditionsMap, "time_allowed", conditions.TimeAllowed, flattenTimeAllowed)
return []interface{}{conditionsMap}
}
func buildPolicyActionsFromResource(d *schema.ResourceData, pp *policyProxy, ctx context.Context) *platformclientv2.Policyactions {
if actions, ok := d.Get("actions").([]interface{}); ok && len(actions) > 0 {
actionsMap, ok := actions[0].(map[string]interface{})
if !ok {
return nil
}
retainRecording := actionsMap["retain_recording"].(bool)
deleteRecording := actionsMap["delete_recording"].(bool)
alwaysDelete := actionsMap["always_delete"].(bool)
meteredAssignmentByAgent := buildAssignMeteredAssignmentByAgent(actionsMap["assign_metered_assignment_by_agent"].([]interface{}), pp, ctx)
assignMeteredEvaluations := buildAssignMeteredEvaluations(actionsMap["assign_metered_evaluations"].([]interface{}), pp, ctx)
return &platformclientv2.Policyactions{
RetainRecording: &retainRecording,
DeleteRecording: &deleteRecording,
AlwaysDelete: &alwaysDelete,
AssignEvaluations: buildEvaluationAssignments(actionsMap["assign_evaluations"].([]interface{}), pp, ctx),
AssignMeteredEvaluations: assignMeteredEvaluations,
AssignMeteredAssignmentByAgent: meteredAssignmentByAgent,
AssignCalibrations: buildAssignCalibrations(actionsMap["assign_calibrations"].([]interface{}), pp, ctx),
AssignSurveys: buildAssignSurveys(actionsMap["assign_surveys"].([]interface{}), pp, ctx),
RetentionDuration: buildRetentionDuration(actionsMap["retention_duration"].([]interface{})),
InitiateScreenRecording: buildInitiateScreenRecording(actionsMap["initiate_screen_recording"].([]interface{})),
MediaTranscriptions: buildMediaTranscriptions(actionsMap["media_transcriptions"].([]interface{})),
IntegrationExport: buildIntegrationExport(actionsMap["integration_export"].([]interface{})),
}
}
return nil
}
func buildUserParams(params []interface{}) *[]platformclientv2.Userparam {
userParams := make([]platformclientv2.Userparam, 0)
for _, param := range params {
paramMap, ok := param.(map[string]interface{})
if !ok {
continue
}
key := paramMap["key"].(string)
value := paramMap["value"].(string)
userParams = append(userParams, platformclientv2.Userparam{
Key: &key,
Value: &value,
})
}
return &userParams
}
func flattenUserParams(params *[]platformclientv2.Userparam) []interface{} {
if params == nil {
return nil
}
paramList := make([]interface{}, 0)
for _, param := range *params {
paramMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(paramMap, "key", param.Key)
resourcedata.SetMapValueIfNotNil(paramMap, "value", param.Value)
paramList = append(paramList, paramMap)
}
return paramList
}
func buildPolicyErrorMessages(messages []interface{}) *[]platformclientv2.Policyerrormessage {
policyErrorMessages := make([]platformclientv2.Policyerrormessage, 0)
for _, message := range messages {
messageMap, ok := message.(map[string]interface{})
if !ok {
continue
}
statusCode := messageMap["status_code"].(int)
userMessage := messageMap["user_message"]
userParamsMessage := messageMap["user_params_message"].(string)
errorCode := messageMap["error_code"].(string)
correlationId := messageMap["correlation_id"].(string)
insertDateString := messageMap["insert_date"].(string)
temp := platformclientv2.Policyerrormessage{
StatusCode: &statusCode,
UserMessage: &userMessage,
UserParamsMessage: &userParamsMessage,
ErrorCode: &errorCode,
CorrelationId: &correlationId,
UserParams: buildUserParams(messageMap["user_params"].([]interface{})),
}
insertDate, insertErr := time.Parse("2006-01-02T15:04:05-0700", insertDateString)
if insertErr == nil {
temp.InsertDate = &insertDate
}
policyErrorMessages = append(policyErrorMessages, temp)
}
return &policyErrorMessages
}
func flattenPolicyErrorMessages(errorMessages *[]platformclientv2.Policyerrormessage) []interface{} {
if errorMessages == nil {
return nil
}
errorMessageList := make([]interface{}, 0)
for _, errorMessage := range *errorMessages {
errorMessageMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(errorMessageMap, "status_code", errorMessage.StatusCode)
resourcedata.SetMapValueIfNotNil(errorMessageMap, "user_message", errorMessage.UserMessage)
resourcedata.SetMapValueIfNotNil(errorMessageMap, "user_params_message", errorMessage.UserParamsMessage)
resourcedata.SetMapValueIfNotNil(errorMessageMap, "error_code", errorMessage.ErrorCode)
resourcedata.SetMapValueIfNotNil(errorMessageMap, "correlation_id", errorMessage.CorrelationId)
if errorMessage.InsertDate != nil && len(errorMessage.InsertDate.String()) > 0 {
temp := *errorMessage.InsertDate
errorMessageMap["insert_date"] = temp.String()
}
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(errorMessageMap, "user_params", errorMessage.UserParams, flattenUserParams)
errorMessageList = append(errorMessageList, errorMessageMap)
}
return errorMessageList
}
func buildPolicyErrors(d *schema.ResourceData) *platformclientv2.Policyerrors {
if errors, ok := d.GetOk("policy_errors"); ok {
if errorsList, ok := errors.([]interface{}); ok || len(errorsList) > 0 {
errorsMap, ok := errorsList[0].(map[string]interface{})
if !ok {
return nil
}
return &platformclientv2.Policyerrors{
PolicyErrorMessages: buildPolicyErrorMessages(errorsMap["policy_error_messages"].([]interface{})),
}
}
}
return nil
}
func flattenPolicyErrors(policyErrors *platformclientv2.Policyerrors) []interface{} {
if policyErrors == nil {
return nil
}
policyErrorsMap := make(map[string]interface{})
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(policyErrorsMap, "policy_error_messages", policyErrors.PolicyErrorMessages, flattenPolicyErrorMessages)
return []interface{}{policyErrorsMap}
}
package resource_cache
import "sync"
type inMemoryCache[T any] struct {
lock sync.Mutex
data map[string]T
}
// Set stores a value in the in-memory cache
func (c *inMemoryCache[T]) Set(key string, value T) {
c.lock.Lock()
defer c.lock.Unlock()
c.data[key] = value
}
func (c *inMemoryCache[T]) Delete(key string) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.data, key)
}
// Get retrieves a value from the in-memory cache
func (c *inMemoryCache[T]) Get(key string) (T, bool) {
c.lock.Lock()
defer c.lock.Unlock()
value, ok := c.data[key]
return value, ok
}
// GetAll retrieves all the values from the in-memory cache
func (c *inMemoryCache[T]) GetAll() []T {
c.lock.Lock()
defer c.lock.Unlock()
var items []T
for _, item := range c.data {
items = append(items, item)
}
return items
}
// GetSize retrieves the size of the in-memory cache
func (c *inMemoryCache[T]) GetSize() int {
c.lock.Lock()
defer c.lock.Unlock()
return len(c.data)
}
package resource_cache
import (
"log"
"terraform-provider-genesyscloud/genesyscloud/tfexporter_state"
)
type CacheInterface[T any] interface {
Set(key string, value T)
Get(key string) (T, bool)
GetAll() []T
GetSize() int
Delete(key string)
}
// NewResourceCache is a factory method to return the cache implementation. We have made this a cache so we can plugin in
func NewResourceCache[T any]() CacheInterface[T] {
return &inMemoryCache[T]{ //This will show as a missing type in goland, but it compiles. I think golang is have a problem resolving this
data: make(map[string]T),
}
}
func SetCache[T any](cache CacheInterface[T], key string, value T) {
if tfexporter_state.IsExporterActive() {
cache.Set(key, value)
}
}
func DeleteCacheItem[T any](cache CacheInterface[T], key string) {
if tfexporter_state.IsExporterActive() {
cache.Delete(key)
}
}
func GetCacheItem[T any](cache CacheInterface[T], key string) *T {
if tfexporter_state.IsExporterActive() {
eg, ok := cache.Get(key)
if ok {
return &eg
}
log.Printf("Resource Data not present in the Cache for %v, will do API call to fetch", key)
}
return nil
}
func GetCache[T any](cache CacheInterface[T]) *[]T {
if tfexporter_state.IsExporterActive() {
items := cache.GetAll()
if items != nil && len(items) > 0 {
return &items
}
log.Print("Cache is empty, will do API calls to fetch data")
}
return nil
}
func GetCacheSize[T any](cache CacheInterface[T]) int {
if tfexporter_state.IsExporterActive() {
return cache.GetSize()
}
return 0
}
package resource_exporter
import (
"context"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"regexp"
"strings"
"sync"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)
var resourceExporters map[string]*ResourceExporter
var resourceExporterMapMutex = sync.RWMutex{}
type ResourceMeta struct {
// Name of the resource to be used in exports
Name string
// Prefix to add to the ID when reading state
IdPrefix string
}
// ResourceIDMetaMap is a map of IDs to ResourceMeta
type ResourceIDMetaMap map[string]*ResourceMeta
type GetAllCustomResourcesFunc func(context.Context) (ResourceIDMetaMap, *DependencyResource, diag.Diagnostics)
// GetAllResourcesFunc is a method that returns all resource IDs
type GetAllResourcesFunc func(context.Context) (ResourceIDMetaMap, diag.Diagnostics)
// RefAttrSettings contains behavior settings for references
type RefAttrSettings struct {
// Referenced resource type
RefType string
// Values that may be set that should not be treated as IDs
AltValues []string
}
type ResourceInfo struct {
State *terraform.InstanceState
Name string
Type string
CtyType cty.Type
}
// RefAttrCustomResolver allows the definition of a custom resolver for an exporter.
type RefAttrCustomResolver struct {
ResolverFunc func(map[string]interface{}, map[string]*ResourceExporter, string) error
ResolveToDataSourceFunc func(map[string]interface{}, any, *platformclientv2.Configuration) (string, string, map[string]interface{}, bool)
}
// CustomFlowResolver allows the definition of a custom resolver for an exporter.
type CustomFlowResolver struct {
ResolverFunc func(map[string]interface{}, string) error
}
type CustomFileWriterSettings struct {
// Custom function for dumping data/media stored in an object in a sub directory along
// with the exported config. For example: prompt audio files, csv data, jps/pngs
RetrieveAndWriteFilesFunc func(string, string, string, map[string]interface{}, interface{}) error
// Sub directory within export folder in which to write files retrieved by RetrieveAndWriteFilesFunc
// For example, the user_prompt resource defines SubDirectory as "audio", so the prompt audio files will
// be written to genesyscloud_tf_export.directory/audio/
// The logic for retrieving and writing data to this dir should be defined in RetrieveAndWriteFilesFunc
SubDirectory string
}
type JsonEncodeRefAttr struct {
// The outer key
Attr string
// The RefAttr nested inside the json data
NestedAttr string
}
type DependencyResource struct {
DependsMap map[string][]string
CyclicDependsList []string
}
// ResourceExporter is an interface to implement for resources that can be exported
type ResourceExporter struct {
// Method to load all resource IDs for a given resource.
// Returned map key should be the ID and the value should be a name to use for the resource.
// Names will be sanitized with part of the ID appended, so it is not required that they be unique
GetResourcesFunc GetAllResourcesFunc
// A map of resource attributes to types that they reference
// Attributes in nested objects can be defined with a '.' separator
RefAttrs map[string]*RefAttrSettings
// AllowZeroValues is a list of attributes that should allow zero values in the export.
// By default zero values are removed from the config due to lack of "null" support in the plugin SDK
AllowZeroValues []string
// AllowEmptyArrays is a list of List attributes that should allow empty arrays in export.
// By default, empty arrays are removed but some array attributes may be required in the schema
// or depending on the API behavior better presented explicitly in the API as empty arrays.
// If the state has this as null or empty array, then the attribute will be returned as an empty array.
AllowEmptyArrays []string
// Some of our dependencies can not be exported properly because they have interdependencies between attributes. You can
// define a map of custom attribute resolvers with an exporter. See resource_genesyscloud_routing_queue for an example of how to define this.
// NOTE: CustomAttributeResolvers should be the exception and not the norm so use them when you have to do logic that will help you
// resolve to the write reference
CustomAttributeResolver map[string]*RefAttrCustomResolver
// RemoveIfMissing is a map of attributes to a list of inner object attributes.
// When all specified inner attributes are missing from an object, that object is removed
RemoveIfMissing map[string][]string
// Map of resource id->names. This is set after a call to loadSanitizedResourceMap
SanitizedResourceMap ResourceIDMetaMap
// List of attributes to exclude from config. This is set by the export configuration.
ExcludedAttributes []string
// Map of attributes that cannot be resolved. E.g. edge Ids which are locked to an org or properties that cannot be retrieved from the API
UnResolvableAttributes map[string]*schema.Schema
// List of attributes which can and should be exported in a jsonencode object rather than as a long escaped string of JSON data.
JsonEncodeAttributes []string
// Attributes that are jsonencode objects, and that contain nested RefAttrs
EncodedRefAttrs map[*JsonEncodeRefAttr]*RefAttrSettings
CustomFileWriter CustomFileWriterSettings
CustomFlowResolver map[string]*CustomFlowResolver
//This a placeholder filter out specific resources from a filter.
FilterResource func(ResourceIDMetaMap, string, []string) ResourceIDMetaMap
// Attributes that are mentioned with custom exports like e164 numbers,rrule should be ensured to export in the correct format (remove hyphens, whitespace, etc.)
CustomValidateExports map[string][]string
mutex sync.RWMutex
}
func (r *ResourceExporter) LoadSanitizedResourceMap(ctx context.Context, name string, filter []string) diag.Diagnostics {
result, err := r.GetResourcesFunc(ctx)
if err != nil {
return err
}
if r.FilterResource != nil {
result = r.FilterResource(result, name, filter)
}
// Lock the Resource Map as it is accessed by goroutines
r.mutex.Lock()
r.SanitizedResourceMap = result
r.mutex.Unlock()
sanitizer := NewSanitizerProvider()
sanitizer.S.Sanitize(r.SanitizedResourceMap)
return nil
}
func (r *ResourceExporter) GetRefAttrSettings(attribute string) *RefAttrSettings {
if r.RefAttrs == nil {
return nil
}
return r.RefAttrs[attribute]
}
func (r *ResourceExporter) GetNestedRefAttrSettings(attribute string) *RefAttrSettings {
for key, val := range r.EncodedRefAttrs {
if key.NestedAttr == attribute {
return val
}
}
return nil
}
func (r *ResourceExporter) ContainsNestedRefAttrs(attribute string) ([]string, bool) {
var nestedAttributes []string
for key := range r.EncodedRefAttrs {
if key.Attr == attribute {
nestedAttributes = append(nestedAttributes, key.NestedAttr)
}
}
return nestedAttributes, len(nestedAttributes) > 0
}
func (r *ResourceExporter) AllowForZeroValues(attribute string) bool {
return lists.ItemInSlice(attribute, r.AllowZeroValues)
}
func (r *ResourceExporter) AllowForEmptyArrays(attribute string) bool {
return lists.ItemInSlice(attribute, r.AllowEmptyArrays)
}
func (r *ResourceExporter) IsJsonEncodable(attribute string) bool {
return lists.ItemInSlice(attribute, r.JsonEncodeAttributes)
}
func (r *ResourceExporter) IsAttributeE164(attribute string) bool {
values, exists := r.CustomValidateExports["E164"]
if !exists {
return false
}
return lists.ItemInSlice(attribute, values)
}
func (r *ResourceExporter) IsAttributeRrule(attribute string) bool {
values, exists := r.CustomValidateExports["rrule"]
if !exists {
return false
}
return lists.ItemInSlice(attribute, values)
}
func (r *ResourceExporter) AddExcludedAttribute(attribute string) {
r.ExcludedAttributes = append(r.ExcludedAttributes, attribute)
}
func (r *ResourceExporter) IsAttributeExcluded(attribute string) bool {
for _, excluded := range r.ExcludedAttributes {
// Excluded if attributes match, or the specified attribute is nested in the excluded attribute
if excluded == attribute || strings.HasPrefix(attribute, excluded+".") {
return true
}
}
return false
}
func (r *ResourceExporter) RemoveFieldIfMissing(attribute string, config map[string]interface{}) bool {
if attrs, ok := r.RemoveIfMissing[attribute]; ok {
// Check if all required inner attributes are missing
missingAll := true
for _, attr := range attrs {
if val, foundInner := config[attr]; foundInner && val != nil {
missingAll = false
break
}
}
return missingAll
}
return false
}
func GetResourceExporters() map[string]*ResourceExporter {
//Make a Copy of the Map
exportCopy := make(map[string]*ResourceExporter, len(resourceExporters))
for k, v := range resourceExporters {
exportCopy[k] = v
}
return exportCopy
}
// terraform-provider-genesyscloud/genesyscloud/tfexporter
func GetAvailableExporterTypes() []string {
exporters := GetResourceExporters()
types := make([]string, len(exporters))
i := 0
for k := range exporters {
types[i] = k
i++
}
return types
}
func escapeRune(s string) string {
// Always replace with an underscore for readability. The appended hash will help ensure uniqueness
return "_"
}
// Resource names must only contain alphanumeric chars, underscores, or dashes
// https://www.terraform.io/docs/language/syntax/configuration.html#identifiers
var unsafeNameChars = regexp.MustCompile(`[^0-9A-Za-z_-]`)
// Resource names must start with a letter or underscore
// https://www.terraform.io/docs/language/syntax/configuration.html#identifiers
var unsafeNameStartingChars = regexp.MustCompile(`[^A-Za-z_]`)
func RegisterExporter(exporterName string, resourceExporter *ResourceExporter) {
resourceExporterMapMutex.Lock()
defer resourceExporterMapMutex.Unlock()
resourceExporters[exporterName] = resourceExporter
}
func SetRegisterExporter(resources map[string]*ResourceExporter) {
resourceExporterMapMutex.Lock()
defer resourceExporterMapMutex.Unlock()
resourceExporters = resources
}
package resource_exporter
import (
"encoding/json"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"regexp"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
)
/*
OutboundCampaignAgentScriptResolver
Forces script_id to reference a data source for the Default Outbound Script
and the returns the script resource type "", the data source ID, and the config of the data source for the tfexporter package to add it to the export.
(We can't pass in the map and add the data source here because it causes a cyclic error between packages resource_exporter and tfexporter,
so instead we pass back all the details tfexporter needs to do it itself)
*/
func OutboundCampaignAgentScriptResolver(configMap map[string]interface{}, value any, sdkConfig *platformclientv2.Configuration) (dsType string, dsID string, dsConfig map[string]interface{}, resolve bool) {
var (
scriptDataSourceConfig = make(map[string]interface{})
scriptDataSourceId = strings.Replace(constants.DefaultOutboundScriptName, " ", "_", -1)
)
scriptId, _ := value.(string)
if IsDefaultOutboundScript(scriptId, sdkConfig) {
scriptDataSourceConfig["name"] = constants.DefaultOutboundScriptName
configMap["script_id"] = fmt.Sprintf("${data.genesyscloud_script.%s.id}", scriptDataSourceId)
return "genesyscloud_script", scriptDataSourceId, scriptDataSourceConfig, true
}
return "", "", nil, false
}
/*
IsDefaultOutboundScript
Takes a script ID and checks if the name of the script equals defaultOutboundScriptName.
If the operation fails, we will just log the error and allow the exporter to include the hard-coded GUID, as opposed to failing.
*/
func IsDefaultOutboundScript(scriptId string, sdkConfig *platformclientv2.Configuration) bool {
if !isValidGuid(scriptId) {
return false
}
apiInstance := platformclientv2.NewScriptsApiWithConfig(sdkConfig)
log.Printf("reading published script %s", scriptId)
data, _, err := apiInstance.GetScriptsPublishedScriptId(scriptId, "")
if err != nil {
log.Printf("failed to read script %s: %v", scriptId, err)
return false
}
log.Printf("read published script %s %s", scriptId, *data.Name)
return *data.Name == constants.DefaultOutboundScriptName
}
func isValidGuid(id string) bool {
matched, err := regexp.MatchString("^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$", id)
if err != nil {
log.Printf("failed to validate format of GUID %s: %v", id, err)
return false
}
return matched
}
/*
MemberGroupsResolver
The resource_genesyscloud_routing_queue object has the concept of bullseye ring with a member_groups attribute.
The routing team has overloaded the meaning of the member_groups so you can id and then define what "type" of id this is.
This causes problems with the exporter because our export process expects id to map to a specific resource.
This customer custom router will look at the member_group_type and resolve whether it is SKILLGROUP, GROUP type. It will then
find the appropriate resource out of the exporters and build a reference appropriately.
*/
func MemberGroupsResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, _ string) error {
var (
resourceID string
memberGroupType = configMap["member_group_type"].(string)
memberGroupID = configMap["member_group_id"].(string)
)
switch memberGroupType {
case "SKILLGROUP":
resourceID = "genesyscloud_routing_skill_group"
case "GROUP":
resourceID = "genesyscloud_group"
case "TEAM":
resourceID = "genesyscloud_team"
default:
return fmt.Errorf("the memberGroupType %s cannot be located. Can not resolve to a reference attribute", memberGroupType)
}
if exporter, ok := exporters[resourceID]; ok {
memberGroupExport, ok := exporter.SanitizedResourceMap[memberGroupID]
if !ok || memberGroupExport == nil {
return fmt.Errorf("could not resolve member group %s to a resource of type %s", memberGroupID, resourceID)
}
exportId := memberGroupExport.Name
configMap["member_group_id"] = fmt.Sprintf("${%s.%s.id}", resourceID, exportId)
} else {
return fmt.Errorf("unable to locate %s in the exporters array. Unable to resolve the ID for the member group resource", resourceID)
}
return nil
}
/*
RuleSetPropertyResolver
For resource_genesyscloud_outbound_ruleset, there is a property called properties which is a map of stings.
When exporting outbound rulesets, if one of the keys in the map is set to an empty string it will be ignored
by the export process. Example: properties = {"contact.Attempts" = ""}.
During the export process the value associated with the key is set to nil.
This custom exporter checks if a key has a value of nil and if it does sets it to an empty string so it is exported.
*/
func RuleSetPropertyResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
if properties, ok := configMap["properties"].(map[string]interface{}); ok {
for key, value := range properties {
if value == nil {
properties[key] = ""
}
}
}
return nil
}
/*
RuleSetSkillPropertyResolver
This property takes a key 'skills' with an array of skill ids wrapped into a string (Example: {'skills': '['skillIdHere']'} ).
This causes problems with the exporter because our export process expects id to map to a specific resource
and we have an array of attributes wrapped in a string.
This customer custom router will look at the skills array if present and resolve each string id find the appropriate resource out of the exporters and build a reference appropriately.
*/
func RuleSetSkillPropertyResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
if exporter, ok := exporters["genesyscloud_routing_skill"]; ok {
skillIDs := configMap["skills"].(string)
if len(skillIDs) == 0 {
return nil
} else {
sanitisedSkillIds := []string{}
skillIDs = skillIDs[1 : len(skillIDs)-1]
skillIdList := strings.Split(skillIDs, ",")
exportId := ""
// Trim the double quotes from each element in the array
for i := 0; i < len(skillIdList); i++ {
skillIdList[i] = strings.Trim(skillIdList[i], "\"")
}
for _, skillId := range skillIdList {
// DEVTOOLING-319: Outbound rulesets can reference skills that no longer exist. Plugin crash if we process a skill that doesn't exist in the skill map, making sure of its existence before proceeding.
value, exists := exporter.SanitizedResourceMap[skillId]
if exists {
exportId = value.Name
sanitisedSkillIds = append(sanitisedSkillIds, fmt.Sprintf("${genesyscloud_routing_skill.%s.id}", exportId))
} else {
log.Printf("Skill '%s' does not exist in the skill map.\n", skillId)
sanitisedSkillIds = append(sanitisedSkillIds, fmt.Sprintf("skill_%s_not_found", skillId))
}
}
jsonData, err := json.Marshal(sanitisedSkillIds)
if err != nil {
return fmt.Errorf("error converting sanitized skill ids array to JSON: %s", err)
}
configMap["skills"] = string(jsonData)
}
} else {
return fmt.Errorf("unable to locate genesyscloud_routing_skill in the exporters array")
}
return nil
}
func FileContentHashResolver(configMap map[string]interface{}, filepath string) error {
configMap["file_content_hash"] = fmt.Sprintf(`${filesha256(var.%s)}`, filepath)
return nil
}
func CampaignStatusResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
if configMap["campaign_status"] != "off" && configMap["campaign_status"] != "on" {
configMap["campaign_status"] = "off"
}
return nil
}
func ReplyEmailAddressSelfReferenceRouteExporterResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
routeId := configMap["route_id"].(string)
currentRouteReference := fmt.Sprintf("${genesyscloud_routing_email_route.%s.id}", resourceName)
if routeId == currentRouteReference {
configMap["self_reference_route"] = true
configMap["route_id"] = nil
}
return nil
}
func ConditionValueResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
if value := configMap["condition_value"]; value == nil {
configMap["condition_value"] = 0
}
return nil
}
package resource_exporter
import (
"hash/fnv"
"log"
"os"
"strconv"
)
type SanitizerProvider struct {
S Sanitizer
}
type Sanitizer interface {
Sanitize(idMetaMap ResourceIDMetaMap)
SanitizeResourceName(inputName string) string
}
// Two different Sanitizer structs one with the original algorithmn
type sanitizerOriginal struct{}
type sanitizerOptimized struct{}
// NewSanitizierProvider returns a Sanitizer. Without a GENESYS_SANITIZER_LEGACY environment variable set it will always use the optimized Sanitizer
func NewSanitizerProvider() *SanitizerProvider {
// Check if the environment variable is set
_, exists := os.LookupEnv("GENESYS_SANITIZER_LEGACY")
//If the GENESYS_SANITIZER_LEGACY is set use the original name sanitizer
if exists {
log.Print("Using the original resource name sanitizer")
return &SanitizerProvider{
S: &sanitizerOriginal{},
}
}
log.Print("Using the optimized resource name sanitizer")
return &SanitizerProvider{
S: &sanitizerOptimized{},
}
}
// Sanitize sanitizes all the resource names using the original algorithm
func (so *sanitizerOriginal) Sanitize(idMetaMap ResourceIDMetaMap) {
for _, meta := range idMetaMap {
meta.Name = so.SanitizeResourceName(meta.Name)
}
}
// SanitizeResourceName sanitizes a single resource name using the original resource name sanitizer
func (so *sanitizerOriginal) SanitizeResourceName(inputName string) string {
name := unsafeNameChars.ReplaceAllStringFunc(inputName, escapeRune)
if name != inputName {
// Append a hash of the original name to ensure uniqueness for similar names
// and that equivalent names are consistent across orgs
algorithm := fnv.New32()
algorithm.Write([]byte(inputName))
name = name + "_" + strconv.FormatUint(uint64(algorithm.Sum32()), 10)
}
if unsafeNameStartingChars.MatchString(string(rune(name[0]))) {
// Terraform does not allow names to begin with a number. Prefix with an underscore instead
name = "_" + name
}
return name
}
// Sanitize sanitizes all resource name using the optimized algorithm
func (sod *sanitizerOptimized) Sanitize(idMetaMap ResourceIDMetaMap) {
// Pull out all the original names of the resources for reference later
originalResourceNames := make(map[string]string)
for k, v := range idMetaMap {
originalResourceNames[k] = v.Name
}
// Iterate over the idMetaMap and sanitize the names of each resource
for _, meta := range idMetaMap {
sanitizedName := sod.SanitizeResourceName(meta.Name)
// If there are more than one resource name that ends up with the same sanitized name,
// append a hash of the original name to ensure uniqueness for names to prevent duplicates
if sanitizedName != meta.Name {
numSeen := 0
for _, originalName := range originalResourceNames {
originalSanitizedName := sod.SanitizeResourceName(originalName)
if sanitizedName == originalSanitizedName {
numSeen++
}
}
if numSeen > 1 {
algorithm := fnv.New32()
algorithm.Write([]byte(meta.Name))
sanitizedName = sanitizedName + "_" + strconv.FormatUint(uint64(algorithm.Sum32()), 10)
}
meta.Name = sanitizedName
}
}
}
// SanitizeResourceName sanitizes a single resource name
func (sod *sanitizerOptimized) SanitizeResourceName(inputName string) string {
name := unsafeNameChars.ReplaceAllStringFunc(inputName, escapeRune)
if unsafeNameStartingChars.MatchString(string(rune(name[0]))) {
// Terraform does not allow names to begin with a number. Prefix with an underscore instead
name = "_" + name
}
return name
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/validators"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/leekchan/timeutil"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllArchitectSchedules(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
archAPI := platformclientv2.NewArchitectApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
schedules, resp, getErr := archAPI.GetArchitectSchedules(pageNum, pageSize, "", "", "", nil)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Failed to get page of schedules error: %s", getErr), resp)
}
if schedules.Entities == nil || len(*schedules.Entities) == 0 {
break
}
for _, schedule := range *schedules.Entities {
resources[*schedule.Id] = &resourceExporter.ResourceMeta{Name: *schedule.Name}
}
}
return resources, nil
}
func ArchitectSchedulesExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllArchitectSchedules),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
},
CustomValidateExports: map[string][]string{
"rrule": {"rrule"},
},
}
}
func ResourceArchitectSchedules() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Architect Schedules",
CreateContext: provider.CreateWithPooledClient(createArchitectSchedules),
ReadContext: provider.ReadWithPooledClient(readArchitectSchedules),
UpdateContext: provider.UpdateWithPooledClient(updateArchitectSchedules),
DeleteContext: provider.DeleteWithPooledClient(deleteArchitectSchedules),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the schedule.",
Type: schema.TypeString,
Required: true,
},
"division_id": {
Description: "The division to which this schedule group will belong. If not set, the home division will be used. If set, you must have all divisions and future divisions selected in your OAuth client role",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Description: "Description of the schedule.",
Type: schema.TypeString,
Optional: true,
},
"start": {
Description: "Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validators.ValidateLocalDateTimes,
},
"end": {
Description: "Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validators.ValidateLocalDateTimes,
},
"rrule": {
Description: "An iCal Recurrence Rule (RRULE) string. It is required to be set for schedules determining when upgrades to the Edge software can be applied.",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validators.ValidateRrule,
},
},
}
}
func createArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
divisionID := d.Get("division_id").(string)
description := d.Get("description").(string)
start := d.Get("start").(string)
end := d.Get("end").(string)
rrule := d.Get("rrule").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archAPI := platformclientv2.NewArchitectApiWithConfig(sdkConfig)
schedStart, err := time.Parse("2006-01-02T15:04:05.000000", start)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Failed to parse date %s", start), err)
}
schedEnd, err := time.Parse("2006-01-02T15:04:05.000000", end)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Failed to parse date %s", end), err)
}
sched := platformclientv2.Schedule{
Name: &name,
Start: &schedStart,
End: &schedEnd,
Rrule: &rrule,
}
// Optional attributes
if description != "" {
sched.Description = &description
}
if divisionID != "" {
sched.Division = &platformclientv2.Writabledivision{Id: &divisionID}
}
log.Printf("Creating schedule %s", name)
schedule, resp, getErr := archAPI.PostArchitectSchedules(sched)
if getErr != nil {
msg := ""
if strings.Contains(fmt.Sprintf("%v", getErr), "routing:schedule:add") {
msg = "\nYou must have all divisions and future divisions selected in your OAuth client role"
}
return util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to create schedule %s | Error: %s MSG: %s", *sched.Name, err, msg), resp)
}
d.SetId(*schedule.Id)
log.Printf("Created schedule %s %s", name, *schedule.Id)
return readArchitectSchedules(ctx, d, meta)
}
func readArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archAPI := platformclientv2.NewArchitectApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceArchitectSchedules(), constants.DefaultConsistencyChecks, "genesyscloud_architect_schedules")
log.Printf("Reading schedule %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
schedule, resp, getErr := archAPI.GetArchitectSchedule(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Failed to read schedule %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Failed to read schedule %s | error: %s", d.Id(), getErr), resp))
}
Start := new(string)
if schedule.Start != nil {
*Start = timeutil.Strftime(schedule.Start, "%Y-%m-%dT%H:%M:%S.%f")
} else {
Start = nil
}
End := new(string)
if schedule.End != nil {
*End = timeutil.Strftime(schedule.End, "%Y-%m-%dT%H:%M:%S.%f")
} else {
End = nil
}
d.Set("name", *schedule.Name)
d.Set("division_id", *schedule.Division.Id)
d.Set("description", nil)
if schedule.Description != nil {
d.Set("description", *schedule.Description)
}
d.Set("start", Start)
d.Set("end", End)
d.Set("rrule", nil)
if schedule.Rrule != nil {
d.Set("rrule", *schedule.Rrule)
}
log.Printf("Read schedule %s %s", d.Id(), *schedule.Name)
return cc.CheckState(d)
})
}
func updateArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
divisionID := d.Get("division_id").(string)
description := d.Get("description").(string)
start := d.Get("start").(string)
end := d.Get("end").(string)
rrule := d.Get("rrule").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archAPI := platformclientv2.NewArchitectApiWithConfig(sdkConfig)
schedStart, err := time.Parse("2006-01-02T15:04:05.000000", start)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Failed to parse date %s", start), err)
}
schedEnd, err := time.Parse("2006-01-02T15:04:05.000000", end)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Failed to parse date %s", end), err)
}
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current schedule version
sched, resp, getErr := archAPI.GetArchitectSchedule(d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to read schedule %s error: %s", d.Id(), err), resp)
}
log.Printf("Updating schedule %s", name)
_, resp, putErr := archAPI.PutArchitectSchedule(d.Id(), platformclientv2.Schedule{
Name: &name,
Version: sched.Version,
Division: &platformclientv2.Writabledivision{Id: &divisionID},
Description: &description,
Start: &schedStart,
End: &schedEnd,
Rrule: &rrule,
})
if putErr != nil {
msg := ""
if strings.Contains(fmt.Sprintf("%v", getErr), "routing:schedule:add") {
msg = "\nYou must have all divisions and future divisions selected in your OAuth client role"
}
return resp, util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to update schedule %s | Error: %s MSG: %s", *sched.Name, putErr, msg), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Finished updating schedule %s", name)
return readArchitectSchedules(ctx, d, meta)
}
func deleteArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
archAPI := platformclientv2.NewArchitectApiWithConfig(sdkConfig)
// DEVTOOLING-311: a schedule linked to a schedule group will not be able to be deleted until that schedule group is deleted. Retryig here to make sure it is cleared properly.
log.Printf("Deleting schedule %s", d.Id())
diagErr := util.RetryWhen(util.IsStatus409, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting schedule %s", d.Id())
resp, err := archAPI.DeleteArchitectSchedule(d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to delete schedule %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
schedule, resp, err := archAPI.GetArchitectSchedule(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// schedule deleted
log.Printf("Deleted schedule %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Error deleting schedule %s | error: %s", d.Id(), err), resp))
}
if schedule.State != nil && *schedule.State == "deleted" {
// schedule deleted
log.Printf("Deleted group %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_architect_schedules", fmt.Sprintf("Schedule %s still exists", d.Id()), resp))
})
}
func GenerateArchitectSchedulesResource(
schedResource1 string,
name string,
divisionId string,
description string,
start string,
end string,
rrule string) string {
return fmt.Sprintf(`resource "genesyscloud_architect_schedules" "%s" {
name = "%s"
division_id = %s
description = "%s"
start = "%s"
end = "%s"
rrule = "%s"
}
`, schedResource1, name, divisionId, description, start, end, rrule)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllAuthDivisions(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
authAPI := platformclientv2.NewAuthorizationApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
divisions, resp, getErr := authAPI.GetAuthorizationDivisions(pageSize, pageNum, "", nil, "", "", false, nil, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Failed to get page of divisions error: %s", getErr), resp)
}
if divisions.Entities == nil || len(*divisions.Entities) == 0 {
break
}
for _, division := range *divisions.Entities {
resources[*division.Id] = &resourceExporter.ResourceMeta{Name: *division.Name}
}
}
return resources, nil
}
func AuthDivisionExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthDivisions),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceAuthDivision() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Authorization Division",
CreateContext: provider.CreateWithPooledClient(createAuthDivision),
ReadContext: provider.ReadWithPooledClient(readAuthDivision),
UpdateContext: provider.UpdateWithPooledClient(updateAuthDivision),
DeleteContext: provider.DeleteWithPooledClient(deleteAuthDivision),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Division name.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "Division description.",
Type: schema.TypeString,
Optional: true,
},
"home": {
Description: "True if this is the home division. This can be set to manage the pre-existing home division. Note: If name attribute is changed, this will cause the auth_division to be dropped and recreated. This will generate a new ID the division. Existing objects with the old division will not be migrated to the new division",
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
},
}
}
func createAuthDivision(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
home := d.Get("home").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
if home {
// Home division must already exist, or it cannot be modified
id, diagErr := util.GetHomeDivisionID()
if diagErr != nil {
return diagErr
}
d.SetId(id)
return updateAuthDivision(ctx, d, meta)
}
log.Printf("Creating division %s", name)
division, resp, err := authAPI.PostAuthorizationDivisions(platformclientv2.Authzdivision{
Name: &name,
Description: &description,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Failed to create division %s error: %s", name, err), resp)
}
d.SetId(*division.Id)
log.Printf("Created division %s %s", name, *division.Id)
return readAuthDivision(ctx, d, meta)
}
func readAuthDivision(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceAuthDivision(), constants.DefaultConsistencyChecks, "genesyscloud_auth_division")
log.Printf("Reading division %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
division, resp, getErr := authAPI.GetAuthorizationDivision(d.Id(), false)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Failed to read division %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Failed to read division %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *division.Name)
if division.Description != nil {
d.Set("description", *division.Description)
} else {
d.Set("description", nil)
}
if division.HomeDivision != nil {
d.Set("home", *division.HomeDivision)
} else {
d.Set("home", nil)
}
log.Printf("Read division %s %s", d.Id(), *division.Name)
return cc.CheckState(d)
})
}
func updateAuthDivision(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
log.Printf("Updating division %s", name)
_, resp, err := authAPI.PutAuthorizationDivision(d.Id(), platformclientv2.Authzdivision{
Name: &name,
Description: &description,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Failed to update division %s error: %s", name, err), resp)
}
log.Printf("Updated division %s", name)
return readAuthDivision(ctx, d, meta)
}
func deleteAuthDivision(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
home := d.Get("home").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
if home {
// Do not delete home division
log.Printf("Not deleting home division %s", name)
return nil
}
// Sometimes a division with resources in it priorly still thinks it is attached to those resources during a destroy run.
// We're retrying again as those resources should detach completely eventually.
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting division %s", name)
resp, err := authAPI.DeleteAuthorizationDivision(d.Id(), false)
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Failed to delete Division %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := authAPI.GetAuthorizationDivision(d.Id(), false)
if err != nil {
if util.IsStatus404(resp) {
// Division deleted
log.Printf("Deleted division %s", name)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Error deleting division %s | error:: %s", name, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_auth_division", fmt.Sprintf("Division %s still exists", name), resp))
})
}
func GenerateAuthDivisionBasic(resourceID string, name string) string {
return GenerateAuthDivisionResource(resourceID, name, util.NullValue, util.FalseValue)
}
func GenerateAuthDivisionResource(
resourceID string,
name string,
description string,
home string) string {
return fmt.Sprintf(`resource "genesyscloud_auth_division" "%s" {
name = "%s"
description = %s
home = %s
}
`, resourceID, name, description, home)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllIdpAdfs(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
_, resp, getErr := idpAPI.GetIdentityprovidersAdfs()
if getErr != nil {
if util.IsStatus404(resp) {
// Don't export if config doesn't exist
return resources, nil
}
return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to get IDP ADFS error: %s", getErr), resp)
}
resources["0"] = &resourceExporter.ResourceMeta{Name: "adfs"}
return resources, nil
}
func IdpAdfsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpAdfs),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceIdpAdfs() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Single Sign-on ADFS Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-microsoft-adfs-single-sign-provider/",
CreateContext: provider.CreateWithPooledClient(createIdpAdfs),
ReadContext: provider.ReadWithPooledClient(readIdpAdfs),
UpdateContext: provider.UpdateWithPooledClient(updateIdpAdfs),
DeleteContext: provider.DeleteWithPooledClient(deleteIdpAdfs),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"certificates": {
Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"issuer_uri": {
Description: "Issuer URI provided by ADFS.",
Type: schema.TypeString,
Required: true,
},
"target_uri": {
Description: "Target URI provided by ADFS.",
Type: schema.TypeString,
Optional: true,
},
"relying_party_identifier": {
Description: "String used to identify Genesys Cloud to ADFS.",
Type: schema.TypeString,
Optional: true,
},
"disabled": {
Description: "True if ADFS is disabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func createIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating IDP ADFS")
d.SetId("adfs")
return updateIdpAdfs(ctx, d, meta)
}
func readIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpAdfs(), constants.DefaultConsistencyChecks, "genesyscloud_idp_adfs")
log.Printf("Reading IDP ADFS")
return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError {
adfs, resp, getErr := idpAPI.GetIdentityprovidersAdfs()
if getErr != nil {
if util.IsStatus404(resp) {
createIdpAdfs(ctx, d, meta)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to read IDP ADFS: %s", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to read IDP ADFS: %s", getErr), resp))
}
if adfs.Certificate != nil {
d.Set("certificates", lists.StringListToInterfaceList([]string{*adfs.Certificate}))
} else if adfs.Certificates != nil {
d.Set("certificates", lists.StringListToInterfaceList(*adfs.Certificates))
} else {
d.Set("certificates", nil)
}
if adfs.IssuerURI != nil {
d.Set("issuer_uri", *adfs.IssuerURI)
} else {
d.Set("issuer_uri", nil)
}
if adfs.SsoTargetURI != nil {
d.Set("target_uri", *adfs.SsoTargetURI)
} else {
d.Set("target_uri", nil)
}
if adfs.RelyingPartyIdentifier != nil {
d.Set("relying_party_identifier", *adfs.RelyingPartyIdentifier)
} else {
d.Set("relying_party_identifier", nil)
}
if adfs.Disabled != nil {
d.Set("disabled", *adfs.Disabled)
} else {
d.Set("disabled", nil)
}
log.Printf("Read IDP ADFS")
return cc.CheckState(d)
})
}
func updateIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
issuerUri := d.Get("issuer_uri").(string)
targetUri := d.Get("target_uri").(string)
relyingPartyID := d.Get("relying_party_identifier").(string)
disabled := d.Get("disabled").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Updating IDP ADFS")
update := platformclientv2.Adfs{
IssuerURI: &issuerUri,
SsoTargetURI: &targetUri,
RelyingPartyIdentifier: &relyingPartyID,
Disabled: &disabled,
}
certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates")
if certificates != nil {
if len(*certificates) == 1 {
update.Certificate = &(*certificates)[0]
}
update.Certificates = certificates
}
_, resp, err := idpAPI.PutIdentityprovidersAdfs(update)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to update IDP ADFS %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated IDP ADFS")
return readIdpAdfs(ctx, d, meta)
}
func deleteIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Deleting IDP ADFS")
_, resp, err := idpAPI.DeleteIdentityprovidersAdfs()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to delete IDP ADFS %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := idpAPI.GetIdentityprovidersAdfs()
if err != nil {
if util.IsStatus404(resp) {
// IDP ADFS deleted
log.Printf("Deleted IDP ADFS")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Error deleting IDP ADFS: %s", err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("IDP ADFS still exists"), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllIdpGeneric(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
_, resp, getErr := idpAPI.GetIdentityprovidersGeneric()
if getErr != nil {
if util.IsStatus404(resp) {
// Don't export if config doesn't exist
return resources, nil
}
return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_generic", fmt.Sprintf("Failed to get IDP Generic error: %s", getErr), resp)
}
resources["0"] = &resourceExporter.ResourceMeta{Name: "generic"}
return resources, nil
}
func IdpGenericExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpGeneric),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceIdpGeneric() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Single Sign-on Generic Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-a-generic-single-sign-on-provider/",
CreateContext: provider.CreateWithPooledClient(createIdpGeneric),
ReadContext: provider.ReadWithPooledClient(readIdpGeneric),
UpdateContext: provider.UpdateWithPooledClient(updateIdpGeneric),
DeleteContext: provider.DeleteWithPooledClient(deleteIdpGeneric),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the provider.",
Type: schema.TypeString,
Required: true,
},
"certificates": {
Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"issuer_uri": {
Description: "Issuer URI provided by the provider.",
Type: schema.TypeString,
Required: true,
},
"target_uri": {
Description: "Target URI provided by the provider.",
Type: schema.TypeString,
Optional: true,
},
"relying_party_identifier": {
Description: "String used to identify Genesys Cloud to the identity provider.",
Type: schema.TypeString,
Optional: true,
},
"disabled": {
Description: "True if Generic provider is disabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"logo_image_data": {
Description: "Base64 encoded SVG image.",
Type: schema.TypeString,
Optional: true,
},
"endpoint_compression": {
Description: "True if the Genesys Cloud authentication request should be compressed.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"name_identifier_format": {
Description: "SAML name identifier format. (urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress | urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName | urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName | urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos | urn:oasis:names:tc:SAML:2.0:nameid-format:entity | urn:oasis:names:tc:SAML:2.0:nameid-format:persistent | urn:oasis:names:tc:SAML:2.0:nameid-format:transient)",
Type: schema.TypeString,
Optional: true,
Default: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
ValidateFunc: validation.StringInSlice([]string{
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos",
"urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
}, false),
},
},
}
}
func createIdpGeneric(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating IDP Generic")
d.SetId("generic")
return updateIdpGeneric(ctx, d, meta)
}
func readIdpGeneric(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpGeneric(), constants.DefaultConsistencyChecks, "genesyscloud_idp_generic")
log.Printf("Reading IDP Generic")
return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError {
generic, resp, getErr := idpAPI.GetIdentityprovidersGeneric()
if getErr != nil {
if util.IsStatus404(resp) {
createIdpGeneric(ctx, d, meta)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_generic", fmt.Sprintf("Failed to read IDP Generic: %s", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_generic", fmt.Sprintf("Failed to read IDP Generic: %s", getErr), resp))
}
if generic.Name != nil {
d.Set("name", *generic.Name)
} else {
d.Set("name", nil)
}
if generic.Certificate != nil {
d.Set("certificates", lists.StringListToInterfaceList([]string{*generic.Certificate}))
} else if generic.Certificates != nil {
d.Set("certificates", lists.StringListToInterfaceList(*generic.Certificates))
} else {
d.Set("certificates", nil)
}
if generic.IssuerURI != nil {
d.Set("issuer_uri", *generic.IssuerURI)
} else {
d.Set("issuer_uri", nil)
}
if generic.SsoTargetURI != nil {
d.Set("target_uri", *generic.SsoTargetURI)
} else {
d.Set("target_uri", nil)
}
if generic.RelyingPartyIdentifier != nil {
d.Set("relying_party_identifier", *generic.RelyingPartyIdentifier)
} else {
d.Set("relying_party_identifier", nil)
}
if generic.Disabled != nil {
d.Set("disabled", *generic.Disabled)
} else {
d.Set("disabled", nil)
}
if generic.LogoImageData != nil {
d.Set("logo_image_data", *generic.LogoImageData)
} else {
d.Set("logo_image_data", nil)
}
if generic.EndpointCompression != nil {
d.Set("endpoint_compression", *generic.EndpointCompression)
} else {
d.Set("endpoint_compression", nil)
}
if generic.NameIdentifierFormat != nil {
d.Set("name_identifier_format", *generic.NameIdentifierFormat)
} else {
d.Set("name_identifier_format", nil)
}
log.Printf("Read IDP Generic")
return cc.CheckState(d)
})
}
func updateIdpGeneric(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
issuerUri := d.Get("issuer_uri").(string)
targetUri := d.Get("target_uri").(string)
relyingPartyID := d.Get("relying_party_identifier").(string)
disabled := d.Get("disabled").(bool)
logoImageData := d.Get("logo_image_data").(string)
endpointCompression := d.Get("endpoint_compression").(bool)
nameIdentifierFormat := d.Get("name_identifier_format").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Updating IDP Generic")
update := platformclientv2.Genericsaml{
Name: &name,
IssuerURI: &issuerUri,
SsoTargetURI: &targetUri,
RelyingPartyIdentifier: &relyingPartyID,
Disabled: &disabled,
LogoImageData: &logoImageData,
EndpointCompression: &endpointCompression,
NameIdentifierFormat: &nameIdentifierFormat,
}
certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates")
if certificates != nil {
if len(*certificates) == 1 {
update.Certificate = &(*certificates)[0]
}
update.Certificates = certificates
}
_, resp, err := idpAPI.PutIdentityprovidersGeneric(update)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_generic", fmt.Sprintf("Failed to update IDP Generic %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated IDP Generic")
return readIdpGeneric(ctx, d, meta)
}
func deleteIdpGeneric(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Deleting IDP Generic")
_, resp, err := idpAPI.DeleteIdentityprovidersGeneric()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_generic", fmt.Sprintf("Failed to delete IDP Generic %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
_, resp, err := idpAPI.GetIdentityprovidersGeneric()
if err != nil {
if util.IsStatus404(resp) {
// IDP Generic deleted
log.Printf("Deleted IDP Generic")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_generic", fmt.Sprintf("Error deleting IDP Generic: %s", err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_generic", fmt.Sprintf("IDP Generic still exists"), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllIdpGsuite(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
_, resp, getErr := idpAPI.GetIdentityprovidersGsuite()
if getErr != nil {
if util.IsStatus404(resp) {
// Don't export if config doesn't exist
return resources, nil
}
return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_gsuite", fmt.Sprintf("Failed to get IDP GSuite error: %s", getErr), resp)
}
resources["0"] = &resourceExporter.ResourceMeta{Name: "gsuite"}
return resources, nil
}
func IdpGsuiteExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpGsuite),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceIdpGsuite() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Single Sign-on GSuite Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-google-g-suite-single-sign-provider/",
CreateContext: provider.CreateWithPooledClient(createIdpGsuite),
ReadContext: provider.ReadWithPooledClient(readIdpGsuite),
UpdateContext: provider.UpdateWithPooledClient(updateIdpGsuite),
DeleteContext: provider.DeleteWithPooledClient(deleteIdpGsuite),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"certificates": {
Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"issuer_uri": {
Description: "Issuer URI provided by GSuite.",
Type: schema.TypeString,
Required: true,
},
"target_uri": {
Description: "Target URI provided by GSuite.",
Type: schema.TypeString,
Optional: true,
},
"relying_party_identifier": {
Description: "String used to identify Genesys Cloud to GSuite.",
Type: schema.TypeString,
Optional: true,
},
"disabled": {
Description: "True if GSuite is disabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func createIdpGsuite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating IDP GSuite")
d.SetId("gsuite")
return updateIdpGsuite(ctx, d, meta)
}
func readIdpGsuite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpGsuite(), constants.DefaultConsistencyChecks, "genesyscloud_idp_gsuite")
log.Printf("Reading IDP GSuite")
return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError {
gsuite, resp, getErr := idpAPI.GetIdentityprovidersGsuite()
if getErr != nil {
if util.IsStatus404(resp) {
createIdpGsuite(ctx, d, meta)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_gsuite", fmt.Sprintf("Failed to read IDP GSuite: %s", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_gsuite", fmt.Sprintf("Failed to read IDP GSuite: %s", getErr), resp))
}
if gsuite.Certificate != nil {
d.Set("certificates", lists.StringListToInterfaceList([]string{*gsuite.Certificate}))
} else if gsuite.Certificates != nil {
d.Set("certificates", lists.StringListToInterfaceList(*gsuite.Certificates))
} else {
d.Set("certificates", nil)
}
if gsuite.IssuerURI != nil {
d.Set("issuer_uri", *gsuite.IssuerURI)
} else {
d.Set("issuer_uri", nil)
}
if gsuite.SsoTargetURI != nil {
d.Set("target_uri", *gsuite.SsoTargetURI)
} else {
d.Set("target_uri", nil)
}
if gsuite.RelyingPartyIdentifier != nil {
d.Set("relying_party_identifier", *gsuite.RelyingPartyIdentifier)
} else {
d.Set("relying_party_identifier", nil)
}
if gsuite.Disabled != nil {
d.Set("disabled", *gsuite.Disabled)
} else {
d.Set("disabled", nil)
}
log.Printf("Read IDP GSuite")
return cc.CheckState(d)
})
}
func updateIdpGsuite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
issuerUri := d.Get("issuer_uri").(string)
targetUri := d.Get("target_uri").(string)
relyingPartyID := d.Get("relying_party_identifier").(string)
disabled := d.Get("disabled").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Updating IDP GSuite")
update := platformclientv2.Gsuite{
IssuerURI: &issuerUri,
SsoTargetURI: &targetUri,
RelyingPartyIdentifier: &relyingPartyID,
Disabled: &disabled,
}
certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates")
if certificates != nil {
if len(*certificates) == 1 {
update.Certificate = &(*certificates)[0]
}
update.Certificates = certificates
}
_, resp, err := idpAPI.PutIdentityprovidersGsuite(update)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_gsuite", fmt.Sprintf("Failed to update IDP GSuite %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated IDP GSuite")
return readIdpGsuite(ctx, d, meta)
}
func deleteIdpGsuite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Deleting IDP GSuite")
_, resp, err := idpAPI.DeleteIdentityprovidersGsuite()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_gsuite", fmt.Sprintf("Failed to delete IDP GSuite %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
_, resp, err := idpAPI.GetIdentityprovidersGsuite()
if err != nil {
if util.IsStatus404(resp) {
// IDP GSuite deleted
log.Printf("Deleted IDP GSuite")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_gsuite", fmt.Sprintf("Error deleting IDP GSuite: %s", err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_gsuite", fmt.Sprintf("IDP GSuite still exists"), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllIdpOkta(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
_, resp, getErr := idpAPI.GetIdentityprovidersOkta()
if getErr != nil {
if util.IsStatus404(resp) {
// Don't export if config doesn't exist
return resources, nil
}
return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to get IDP okta error: %s", getErr), resp)
}
resources["0"] = &resourceExporter.ResourceMeta{Name: "okta"}
return resources, nil
}
func IdpOktaExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpOkta),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceIdpOkta() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Single Sign-on Okta Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-okta-as-a-single-sign-on-provider/",
CreateContext: provider.CreateWithPooledClient(createIdpOkta),
ReadContext: provider.ReadWithPooledClient(readIdpOkta),
UpdateContext: provider.UpdateWithPooledClient(updateIdpOkta),
DeleteContext: provider.DeleteWithPooledClient(deleteIdpOkta),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"certificates": {
Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"issuer_uri": {
Description: "Issuer URI provided by Okta.",
Type: schema.TypeString,
Required: true,
},
"target_uri": {
Description: "Target URI provided by Okta.",
Type: schema.TypeString,
Optional: true,
},
"disabled": {
Description: "True if Okta is disabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func createIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating IDP Okta")
d.SetId("okta")
return updateIdpOkta(ctx, d, meta)
}
func readIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpOkta(), constants.DefaultConsistencyChecks, "genesyscloud_idp_okta")
log.Printf("Reading IDP Okta")
return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError {
okta, resp, getErr := idpAPI.GetIdentityprovidersOkta()
if getErr != nil {
if util.IsStatus404(resp) {
createIdpOkta(ctx, d, meta)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to read IDP Okta: %s", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to read IDP Okta: %s", getErr), resp))
}
if okta.Certificate != nil {
d.Set("certificates", lists.StringListToInterfaceList([]string{*okta.Certificate}))
} else if okta.Certificates != nil {
d.Set("certificates", lists.StringListToInterfaceList(*okta.Certificates))
} else {
d.Set("certificates", nil)
}
if okta.IssuerURI != nil {
d.Set("issuer_uri", *okta.IssuerURI)
} else {
d.Set("issuer_uri", nil)
}
if okta.SsoTargetURI != nil {
d.Set("target_uri", *okta.SsoTargetURI)
} else {
d.Set("target_uri", nil)
}
if okta.Disabled != nil {
d.Set("disabled", *okta.Disabled)
} else {
d.Set("disabled", nil)
}
log.Printf("Read IDP Okta")
return cc.CheckState(d)
})
}
func updateIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
issuerUri := d.Get("issuer_uri").(string)
targetUri := d.Get("target_uri").(string)
disabled := d.Get("disabled").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Updating IDP Okta")
update := platformclientv2.Okta{
IssuerURI: &issuerUri,
SsoTargetURI: &targetUri,
Disabled: &disabled,
}
certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates")
if certificates != nil {
if len(*certificates) == 1 {
update.Certificate = &(*certificates)[0]
}
update.Certificates = certificates
}
_, resp, err := idpAPI.PutIdentityprovidersOkta(update)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to update IDP okta %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated IDP Okta")
return readIdpOkta(ctx, d, meta)
}
func deleteIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Deleting IDP Okta")
_, resp, err := idpAPI.DeleteIdentityprovidersOkta()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to delete IDP okta %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
_, resp, err := idpAPI.GetIdentityprovidersOkta()
if err != nil {
if util.IsStatus404(resp) {
// IDP Okta deleted
log.Printf("Deleted IDP Okta")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Error deleting IDP Okta: %s", err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("IDP Okta still exists"), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllIdpOnelogin(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
_, resp, getErr := idpAPI.GetIdentityprovidersOnelogin()
if getErr != nil {
if util.IsStatus404(resp) {
// Don't export if config doesn't exist
return resources, nil
}
return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Failed to get IDP Onelogin error: %s", getErr), resp)
}
resources["0"] = &resourceExporter.ResourceMeta{Name: "onelogin"}
return resources, nil
}
func IdpOneloginExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpOnelogin),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceIdpOnelogin() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Single Sign-on OneLogin Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-onelogin-as-single-sign-on-provider/",
CreateContext: provider.CreateWithPooledClient(createIdpOnelogin),
ReadContext: provider.ReadWithPooledClient(readIdpOnelogin),
UpdateContext: provider.UpdateWithPooledClient(updateIdpOnelogin),
DeleteContext: provider.DeleteWithPooledClient(deleteIdpOnelogin),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"certificates": {
Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"issuer_uri": {
Description: "Issuer URI provided by OneLogin.",
Type: schema.TypeString,
Required: true,
},
"target_uri": {
Description: "Target URI provided by OneLogin.",
Type: schema.TypeString,
Optional: true,
},
"disabled": {
Description: "True if OneLogin is disabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func createIdpOnelogin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating IDP Onelogin")
d.SetId("onelogin")
return updateIdpOnelogin(ctx, d, meta)
}
func readIdpOnelogin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpOnelogin(), constants.DefaultConsistencyChecks, "genesyscloud_idp_onelogin")
log.Printf("Reading IDP Onelogin")
return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError {
onelogin, resp, getErr := idpAPI.GetIdentityprovidersOnelogin()
if getErr != nil {
if util.IsStatus404(resp) {
createIdpOkta(ctx, d, meta)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Failed to read IDP Onelogin: %s", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Failed to read IDP Onelogin: %s", getErr), resp))
}
if onelogin.Certificate != nil {
d.Set("certificates", lists.StringListToInterfaceList([]string{*onelogin.Certificate}))
} else if onelogin.Certificates != nil {
d.Set("certificates", lists.StringListToInterfaceList(*onelogin.Certificates))
} else {
d.Set("certificates", nil)
}
if onelogin.IssuerURI != nil {
d.Set("issuer_uri", *onelogin.IssuerURI)
} else {
d.Set("issuer_uri", nil)
}
if onelogin.SsoTargetURI != nil {
d.Set("target_uri", *onelogin.SsoTargetURI)
} else {
d.Set("target_uri", nil)
}
if onelogin.Disabled != nil {
d.Set("disabled", *onelogin.Disabled)
} else {
d.Set("disabled", nil)
}
log.Printf("Read IDP Onelogin")
return cc.CheckState(d)
})
}
func updateIdpOnelogin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
issuerUri := d.Get("issuer_uri").(string)
targetUri := d.Get("target_uri").(string)
disabled := d.Get("disabled").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Updating IDP Onelogin")
update := platformclientv2.Onelogin{
IssuerURI: &issuerUri,
SsoTargetURI: &targetUri,
Disabled: &disabled,
}
certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates")
if certificates != nil {
if len(*certificates) == 1 {
update.Certificate = &(*certificates)[0]
}
update.Certificates = certificates
}
_, resp, err := idpAPI.PutIdentityprovidersOnelogin(update)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Failed to update IDP Onelogin %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated IDP Onelogin")
return readIdpOnelogin(ctx, d, meta)
}
func deleteIdpOnelogin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Deleting IDP Onelogin")
_, resp, err := idpAPI.DeleteIdentityprovidersOnelogin()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Failed to updadeletete IDP Onelogin %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
_, resp, err := idpAPI.GetIdentityprovidersOnelogin()
if err != nil {
if util.IsStatus404(resp) {
// IDP Onelogin deleted
log.Printf("Deleted IDP Onelogin")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Error deleting IDP Onelogin: %s", err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("IDP Onelogin still exists"), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllIdpPing(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
_, resp, getErr := idpAPI.GetIdentityprovidersPing()
if getErr != nil {
if util.IsStatus404(resp) {
// Don't export if config doesn't exist
return resources, nil
}
return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_ping", fmt.Sprintf("Failed to get IDP Ping error: %s", getErr), resp)
}
resources["0"] = &resourceExporter.ResourceMeta{Name: "ping"}
return resources, nil
}
func IdpPingExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpPing),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceIdpPing() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Single Sign-on Ping Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-ping-identity-single-sign-provider/",
CreateContext: provider.CreateWithPooledClient(createIdpPing),
ReadContext: provider.ReadWithPooledClient(readIdpPing),
UpdateContext: provider.UpdateWithPooledClient(updateIdpPing),
DeleteContext: provider.DeleteWithPooledClient(deleteIdpPing),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"certificates": {
Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"issuer_uri": {
Description: "Issuer URI provided by Ping.",
Type: schema.TypeString,
Required: true,
},
"target_uri": {
Description: "Target URI provided by Ping.",
Type: schema.TypeString,
Optional: true,
},
"relying_party_identifier": {
Description: "String used to identify Genesys Cloud to Ping.",
Type: schema.TypeString,
Optional: true,
},
"disabled": {
Description: "True if Ping is disabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func createIdpPing(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating IDP Ping")
d.SetId("ping")
return updateIdpPing(ctx, d, meta)
}
func readIdpPing(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpPing(), constants.DefaultConsistencyChecks, "genesyscloud_idp_ping")
log.Printf("Reading IDP Ping")
return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError {
ping, resp, getErr := idpAPI.GetIdentityprovidersPing()
if getErr != nil {
if util.IsStatus404(resp) {
createIdpPing(ctx, d, meta)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_ping", fmt.Sprintf("Failed to read IDP Ping: %s", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_ping", fmt.Sprintf("Failed to read IDP Ping: %s", getErr), resp))
}
if ping.Certificate != nil {
d.Set("certificates", lists.StringListToInterfaceList([]string{*ping.Certificate}))
} else if ping.Certificates != nil {
d.Set("certificates", lists.StringListToInterfaceList(*ping.Certificates))
} else {
d.Set("certificates", nil)
}
if ping.IssuerURI != nil {
d.Set("issuer_uri", *ping.IssuerURI)
} else {
d.Set("issuer_uri", nil)
}
if ping.SsoTargetURI != nil {
d.Set("target_uri", *ping.SsoTargetURI)
} else {
d.Set("target_uri", nil)
}
if ping.RelyingPartyIdentifier != nil {
d.Set("relying_party_identifier", *ping.RelyingPartyIdentifier)
} else {
d.Set("relying_party_identifier", nil)
}
if ping.Disabled != nil {
d.Set("disabled", *ping.Disabled)
} else {
d.Set("disabled", nil)
}
log.Printf("Read IDP Ping")
return cc.CheckState(d)
})
}
func updateIdpPing(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
issuerUri := d.Get("issuer_uri").(string)
targetUri := d.Get("target_uri").(string)
relyingPartyID := d.Get("relying_party_identifier").(string)
disabled := d.Get("disabled").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Updating IDP Ping")
update := platformclientv2.Pingidentity{
IssuerURI: &issuerUri,
SsoTargetURI: &targetUri,
RelyingPartyIdentifier: &relyingPartyID,
Disabled: &disabled,
}
certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates")
if certificates != nil {
if len(*certificates) == 1 {
update.Certificate = &(*certificates)[0]
}
update.Certificates = certificates
}
_, resp, err := idpAPI.PutIdentityprovidersPing(update)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_ping", fmt.Sprintf("Failed to update IDP Ping %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated IDP Ping")
return readIdpPing(ctx, d, meta)
}
func deleteIdpPing(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig)
log.Printf("Deleting IDP Ping")
_, resp, err := idpAPI.DeleteIdentityprovidersPing()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_idp_ping", fmt.Sprintf("Failed to delete IDP Ping %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
_, resp, err := idpAPI.GetIdentityprovidersPing()
if err != nil {
if util.IsStatus404(resp) {
// IDP Ping deleted
log.Printf("Deleted IDP Ping")
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_ping", fmt.Sprintf("Error deleting IDP Ping: %s", err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_ping", fmt.Sprintf("IDP Ping still exists"), resp))
})
}
package genesyscloud
import (
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
func SetRegistrar(l registrar.Registrar) {
registerDataSources(l)
registerResources(l)
registerExporters(l)
}
func registerDataSources(l registrar.Registrar) {
l.RegisterDataSource("genesyscloud_routing_wrapupcode", DataSourceRoutingWrapupcode())
l.RegisterDataSource("genesyscloud_location", DataSourceLocation())
l.RegisterDataSource("genesyscloud_auth_division_home", DataSourceAuthDivisionHome())
l.RegisterDataSource("genesyscloud_architect_schedules", DataSourceSchedule())
l.RegisterDataSource("genesyscloud_auth_division", dataSourceAuthDivision())
l.RegisterDataSource("genesyscloud_auth_division_home", DataSourceAuthDivisionHome())
l.RegisterDataSource("genesyscloud_journey_action_map", dataSourceJourneyActionMap())
l.RegisterDataSource("genesyscloud_journey_action_template", dataSourceJourneyActionTemplate())
l.RegisterDataSource("genesyscloud_journey_outcome", dataSourceJourneyOutcome())
l.RegisterDataSource("genesyscloud_journey_segment", dataSourceJourneySegment())
l.RegisterDataSource("genesyscloud_knowledge_knowledgebase", dataSourceKnowledgeKnowledgebase())
l.RegisterDataSource("genesyscloud_knowledge_category", dataSourceKnowledgeCategory())
l.RegisterDataSource("genesyscloud_knowledge_label", dataSourceKnowledgeLabel())
l.RegisterDataSource("genesyscloud_location", DataSourceLocation())
l.RegisterDataSource("genesyscloud_organizations_me", DataSourceOrganizationsMe())
l.RegisterDataSource("genesyscloud_quality_forms_evaluation", DataSourceQualityFormsEvaluations())
l.RegisterDataSource("genesyscloud_quality_forms_survey", dataSourceQualityFormsSurvey())
l.RegisterDataSource("genesyscloud_routing_language", dataSourceRoutingLanguage())
l.RegisterDataSource("genesyscloud_routing_settings", dataSourceRoutingSettings())
l.RegisterDataSource("genesyscloud_routing_skill", dataSourceRoutingSkill())
l.RegisterDataSource("genesyscloud_routing_skill_group", dataSourceRoutingSkillGroup())
l.RegisterDataSource("genesyscloud_routing_email_domain", DataSourceRoutingEmailDomain())
l.RegisterDataSource("genesyscloud_routing_utilization_label", dataSourceRoutingUtilizationLabel())
l.RegisterDataSource("genesyscloud_routing_wrapupcode", DataSourceRoutingWrapupcode())
l.RegisterDataSource("genesyscloud_user", DataSourceUser())
l.RegisterDataSource("genesyscloud_widget_deployment", dataSourceWidgetDeployments())
}
func registerResources(l registrar.Registrar) {
l.RegisterResource("genesyscloud_location", ResourceLocation())
l.RegisterResource("genesyscloud_architect_schedules", ResourceArchitectSchedules())
l.RegisterResource("genesyscloud_auth_division", ResourceAuthDivision())
l.RegisterResource("genesyscloud_idp_adfs", ResourceIdpAdfs())
l.RegisterResource("genesyscloud_idp_generic", ResourceIdpGeneric())
l.RegisterResource("genesyscloud_idp_gsuite", ResourceIdpGsuite())
l.RegisterResource("genesyscloud_idp_okta", ResourceIdpOkta())
l.RegisterResource("genesyscloud_idp_onelogin", ResourceIdpOnelogin())
l.RegisterResource("genesyscloud_idp_ping", ResourceIdpPing())
l.RegisterResource("genesyscloud_journey_action_map", ResourceJourneyActionMap())
l.RegisterResource("genesyscloud_journey_action_template", ResourceJourneyActionTemplate())
l.RegisterResource("genesyscloud_journey_outcome", ResourceJourneyOutcome())
l.RegisterResource("genesyscloud_journey_segment", ResourceJourneySegment())
l.RegisterResource("genesyscloud_knowledge_knowledgebase", ResourceKnowledgeKnowledgebase())
l.RegisterResource("genesyscloud_knowledge_document", ResourceKnowledgeDocument())
l.RegisterResource("genesyscloud_knowledge_v1_document", ResourceKnowledgeDocumentV1())
l.RegisterResource("genesyscloud_knowledge_document_variation", ResourceKnowledgeDocumentVariation())
l.RegisterResource("genesyscloud_knowledge_category", ResourceKnowledgeCategory())
l.RegisterResource("genesyscloud_knowledge_v1_category", ResourceKnowledgeCategoryV1())
l.RegisterResource("genesyscloud_knowledge_label", ResourceKnowledgeLabel())
l.RegisterResource("genesyscloud_location", ResourceLocation())
l.RegisterResource("genesyscloud_quality_forms_evaluation", ResourceEvaluationForm())
l.RegisterResource("genesyscloud_quality_forms_survey", ResourceSurveyForm())
l.RegisterResource("genesyscloud_routing_email_domain", ResourceRoutingEmailDomain())
l.RegisterResource("genesyscloud_routing_language", ResourceRoutingLanguage())
l.RegisterResource("genesyscloud_routing_skill", ResourceRoutingSkill())
l.RegisterResource("genesyscloud_routing_skill_group", ResourceRoutingSkillGroup())
l.RegisterResource("genesyscloud_routing_settings", ResourceRoutingSettings())
l.RegisterResource("genesyscloud_routing_utilization", ResourceRoutingUtilization())
l.RegisterResource("genesyscloud_routing_utilization_label", ResourceRoutingUtilizationLabel())
l.RegisterResource("genesyscloud_routing_wrapupcode", ResourceRoutingWrapupCode())
l.RegisterResource("genesyscloud_user", ResourceUser())
l.RegisterResource("genesyscloud_widget_deployment", ResourceWidgetDeployment())
}
func registerExporters(l registrar.Registrar) {
l.RegisterExporter("genesyscloud_architect_schedules", ArchitectSchedulesExporter())
l.RegisterExporter("genesyscloud_auth_division", AuthDivisionExporter())
l.RegisterExporter("genesyscloud_idp_adfs", IdpAdfsExporter())
l.RegisterExporter("genesyscloud_idp_generic", IdpGenericExporter())
l.RegisterExporter("genesyscloud_idp_gsuite", IdpGsuiteExporter())
l.RegisterExporter("genesyscloud_idp_okta", IdpOktaExporter())
l.RegisterExporter("genesyscloud_idp_onelogin", IdpOneloginExporter())
l.RegisterExporter("genesyscloud_idp_ping", IdpPingExporter())
l.RegisterExporter("genesyscloud_journey_action_map", JourneyActionMapExporter())
l.RegisterExporter("genesyscloud_journey_action_template", JourneyActionTemplateExporter())
l.RegisterExporter("genesyscloud_journey_outcome", JourneyOutcomeExporter())
l.RegisterExporter("genesyscloud_journey_segment", JourneySegmentExporter())
l.RegisterExporter("genesyscloud_knowledge_knowledgebase", KnowledgeKnowledgebaseExporter())
l.RegisterExporter("genesyscloud_knowledge_document", KnowledgeDocumentExporter())
l.RegisterExporter("genesyscloud_knowledge_category", KnowledgeCategoryExporter())
l.RegisterExporter("genesyscloud_location", LocationExporter())
l.RegisterExporter("genesyscloud_quality_forms_evaluation", EvaluationFormExporter())
l.RegisterExporter("genesyscloud_quality_forms_survey", SurveyFormExporter())
l.RegisterExporter("genesyscloud_routing_email_domain", RoutingEmailDomainExporter())
l.RegisterExporter("genesyscloud_routing_language", RoutingLanguageExporter())
l.RegisterExporter("genesyscloud_routing_settings", RoutingSettingsExporter())
l.RegisterExporter("genesyscloud_routing_skill", RoutingSkillExporter())
l.RegisterExporter("genesyscloud_routing_skill_group", ResourceSkillGroupExporter())
l.RegisterExporter("genesyscloud_routing_utilization", RoutingUtilizationExporter())
l.RegisterExporter("genesyscloud_routing_utilization_label", RoutingUtilizationLabelExporter())
l.RegisterExporter("genesyscloud_routing_wrapupcode", RoutingWrapupCodeExporter())
l.RegisterExporter("genesyscloud_user", UserExporter())
l.RegisterExporter("genesyscloud_widget_deployment", WidgetDeploymentExporter())
l.RegisterExporter("genesyscloud_knowledge_v1_document", KnowledgeDocumentExporterV1())
l.RegisterExporter("genesyscloud_knowledge_document_variation", KnowledgeDocumentVariationExporter())
l.RegisterExporter("genesyscloud_knowledge_label", KnowledgeLabelExporter())
l.RegisterExporter("genesyscloud_knowledge_v1_category", KnowledgeCategoryExporterV1())
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/validators"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/util/stringmap"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"terraform-provider-genesyscloud/genesyscloud/util/typeconv"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
journeyActionMapSchema = map[string]*schema.Schema{
"is_active": {
Description: "Whether the action map is active.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"display_name": {
Description: "Display name of the action map.",
Type: schema.TypeString,
Required: true,
},
"trigger_with_segments": {
Description: "Trigger action map if any segment in the list is assigned to a given customer.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"trigger_with_event_conditions": {
Description: "List of event conditions that must be satisfied to trigger the action map.",
Type: schema.TypeSet,
Optional: true,
Elem: eventConditionResource,
},
"trigger_with_outcome_probability_conditions": {
Description: "Probability conditions for outcomes that must be satisfied to trigger the action map.",
Type: schema.TypeSet,
Optional: true,
Elem: outcomeProbabilityConditionResource,
},
"page_url_conditions": {
Description: "URL conditions that a page must match for web actions to be displayable.",
Type: schema.TypeSet,
Optional: true,
Elem: urlConditionResource,
},
"activation": {
Description: "Type of activation.",
Type: schema.TypeSet,
Required: true,
MaxItems: 1,
Elem: activationResource,
},
"weight": {
Description: "Weight of the action map with higher number denoting higher weight. Low=1, Medium=2, High=3.",
Type: schema.TypeInt,
Optional: true,
Default: 2,
ValidateFunc: validation.IntBetween(1, 3),
},
"action": {
Description: "The action that will be executed if this action map is triggered.",
Type: schema.TypeSet,
Required: true,
MaxItems: 1,
Elem: actionMapActionResource,
},
"action_map_schedule_groups": {
Description: "The action map's associated schedule groups.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: actionMapScheduleGroupsResource,
},
"ignore_frequency_cap": {
Description: "Override organization-level frequency cap and always offer web engagements from this action map.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"start_date": {
Description: "Timestamp at which the action map is scheduled to start firing. Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000.",
Type: schema.TypeString,
Required: true, // Now is the default value for this field. Better to make it required.
ValidateDiagFunc: validators.ValidateLocalDateTimes,
},
"end_date": {
Description: "Timestamp at which the action map is scheduled to stop firing. Date time is represented as an ISO-8601 string without a timezone. For example: 2006-01-02T15:04:05.000000.",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validators.ValidateLocalDateTimes,
},
}
eventConditionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Description: "The event key.",
Type: schema.TypeString,
Required: true,
},
"values": {
Description: "The event values.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"operator": {
Description: "The comparison operator. Valid values: containsAll, containsAny, notContainsAll, notContainsAny, equal, notEqual, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, startsWith, endsWith.",
Type: schema.TypeString,
Optional: true,
Default: "equal",
ValidateFunc: validation.StringInSlice([]string{"containsAll", "containsAny", "notContainsAll", "notContainsAny", "equal", "notEqual", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "startsWith", "endsWith"}, false),
},
"stream_type": {
Description: "The stream type for which this condition can be satisfied. Valid values: Web, Custom, Conversation.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Web", "Custom", "Conversation"}, false),
},
"session_type": {
Description: "The session type for which this condition can be satisfied.",
Type: schema.TypeString,
Required: true,
},
"event_name": {
Description: "The name of the event for which this condition can be satisfied.",
Type: schema.TypeString,
Optional: true,
},
},
}
outcomeProbabilityConditionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"outcome_id": {
Description: "The outcome ID.",
Type: schema.TypeString,
Required: true,
},
"maximum_probability": {
Description: "Probability value for the selected outcome at or above which the action map will trigger.",
Type: schema.TypeFloat,
Required: true,
},
"probability": {
Description: "Additional probability condition, where if set, the action map will trigger if the current outcome probability is lower or equal to the value.",
Type: schema.TypeFloat,
Optional: true,
},
},
}
urlConditionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"values": {
Description: "The URL condition value.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"operator": {
Description: "The comparison operator. Valid values: containsAll, containsAny, notContainsAll, notContainsAny, equal, notEqual, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, startsWith, endsWith.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"containsAll", "containsAny", "notContainsAll", "notContainsAny", "equal", "notEqual", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "startsWith", "endsWith"}, false),
},
},
}
activationResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "Type of activation. Valid values: immediate, on-next-visit, on-next-session, delay.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"immediate", "on-next-visit", "on-next-session", "delay"}, false),
},
"delay_in_seconds": {
Description: "Activation delay time amount.",
Type: schema.TypeInt,
Optional: true,
},
},
}
actionMapActionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"action_template_id": {
Description: "Action template associated with the action map. For media type contentOffer.",
Type: schema.TypeString,
Optional: true,
},
"media_type": {
Description: "Media type of action. Valid values: webchat, webMessagingOffer, contentOffer, architectFlow, openAction.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"webchat", "webMessagingOffer", "contentOffer", "architectFlow", "openAction"}, false),
},
"architect_flow_fields": {
Description: "Architect Flow Id and input contract. For media type architectFlow.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: architectFlowFieldsResource,
},
"web_messaging_offer_fields": {
Description: "Admin-configurable fields of a web messaging offer action. For media type webMessagingOffer.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: webMessagingOfferFieldsResource,
},
"open_action_fields": {
Description: "Admin-configurable fields of an open action. For media type openAction.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: openActionFieldsResource,
},
"is_pacing_enabled": {
Description: "Whether this action should be throttled.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}
architectFlowFieldsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"architect_flow_id": {
Description: "The architect flow.",
Type: schema.TypeString,
Required: true,
},
"flow_request_mappings": {
Description: "Collection of Architect Flow Request Mappings to use.",
Type: schema.TypeSet,
Optional: true,
Elem: requestMappingResource,
},
},
}
requestMappingResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the Integration Action Attribute to supply the value for",
Type: schema.TypeString,
Required: true,
},
"attribute_type": {
Description: "Type of the value supplied. Valid values: String, Number, Integer, Boolean.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"String", "Number", "Integer", "Boolean"}, false),
},
"mapping_type": {
Description: "Method of finding value to use with Attribute. Valid values: Lookup, HardCoded.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Lookup", "HardCoded"}, false),
},
"value": {
Description: "Value to supply for the specified Attribute",
Type: schema.TypeString,
Required: true,
},
},
}
webMessagingOfferFieldsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"offer_text": {
Description: "Text value to be used when inviting a visitor to engage with a web messaging offer.",
Type: schema.TypeString,
Optional: true,
},
"architect_flow_id": {
Description: "Flow to be invoked, overrides default flow when specified.",
Type: schema.TypeString,
Optional: true,
},
},
}
openActionFieldsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"open_action": {
Description: "The specific type of the open action.",
Type: schema.TypeSet,
Required: true,
MaxItems: 1,
Elem: domainEntityRefResource,
},
"configuration_fields": {
Description: "Custom fields defined in the schema referenced by the open action type selected.",
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
},
}
domainEntityRefResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Description: "Id.",
Type: schema.TypeString,
Required: true,
},
"name": {
Description: "Name.",
Type: schema.TypeString,
Required: true,
},
},
}
actionMapScheduleGroupsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"action_map_schedule_group_id": {
Description: "The actions map's associated schedule group.",
Type: schema.TypeString,
Required: true,
},
"emergency_action_map_schedule_group_id": {
Description: "The action map's associated emergency schedule group.",
Type: schema.TypeString,
Optional: true,
},
},
}
)
func getAllJourneyActionMaps(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
journeyApi := platformclientv2.NewJourneyApiWithConfig(clientConfig)
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
actionMaps, resp, getErr := journeyApi.GetJourneyActionmaps(pageNum, pageSize, "", "", "", nil, nil, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("failed to get page of journey action maps error: %s", getErr), resp)
}
if actionMaps.Entities == nil || len(*actionMaps.Entities) == 0 {
break
}
for _, actionMap := range *actionMaps.Entities {
resources[*actionMap.Id] = &resourceExporter.ResourceMeta{Name: *actionMap.DisplayName}
}
pageCount = *actionMaps.PageCount
}
return resources, nil
}
func JourneyActionMapExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllJourneyActionMaps),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"trigger_with_segments": {RefType: "genesyscloud_journey_segment"},
"trigger_with_outcome_probability_conditions.outcome_id": {RefType: "genesyscloud_journey_outcome"},
"action.architect_flow_fields.architect_flow_id": {RefType: "genesyscloud_flow"},
"action_map_schedule_groups.action_map_schedule_group_id": {RefType: "genesyscloud_architect_schedulegroups"},
"action_map_schedule_groups.emergency_action_map_schedule_group_id": {RefType: "genesyscloud_architect_schedulegroups"},
"action.action_template_id": {RefType: "genesyscloud_journey_action_template"},
},
}
}
func ResourceJourneyActionMap() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Journey Action Map",
CreateContext: provider.CreateWithPooledClient(createJourneyActionMap),
ReadContext: provider.ReadWithPooledClient(readJourneyActionMap),
UpdateContext: provider.UpdateWithPooledClient(updateJourneyActionMap),
DeleteContext: provider.DeleteWithPooledClient(deleteJourneyActionMap),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: journeyActionMapSchema,
}
}
func createJourneyActionMap(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
actionMap := buildSdkActionMap(d)
log.Printf("Creating journey action map %s", *actionMap.DisplayName)
result, resp, err := journeyApi.PostJourneyActionmaps(*actionMap)
if err != nil {
input, _ := util.InterfaceToJson(*actionMap)
return util.BuildAPIDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("failed to create journey action map %s: %s\n(input: %+v)", *actionMap.DisplayName, err, input), resp)
}
d.SetId(*result.Id)
log.Printf("Created journey action map %s %s", *result.DisplayName, *result.Id)
return readJourneyActionMap(ctx, d, meta)
}
func readJourneyActionMap(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceJourneyActionMap(), constants.DefaultConsistencyChecks, "genesyscloud_journey_action_map")
log.Printf("Reading journey action map %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
actionMap, resp, getErr := journeyApi.GetJourneyActionmap(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("failed to read journey action map %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("failed to read journey action map %s | error: %s", d.Id(), getErr), resp))
}
flattenActionMap(d, actionMap)
log.Printf("Read journey action map %s %s", d.Id(), *actionMap.DisplayName)
return cc.CheckState(d)
})
}
func updateJourneyActionMap(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
patchActionMap := buildSdkPatchActionMap(d)
log.Printf("Updating journey action map %s", d.Id())
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current journey action map version
actionMap, resp, getErr := journeyApi.GetJourneyActionmap(d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("failed to read journey action map %s error: %s", d.Id(), getErr), resp)
}
patchActionMap.Version = actionMap.Version
_, resp, patchErr := journeyApi.PatchJourneyActionmap(d.Id(), *patchActionMap)
if patchErr != nil {
input, _ := util.InterfaceToJson(*patchActionMap)
return resp, util.BuildAPIDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("Error updating journey action map %s: %s\n(input: %+v)", *patchActionMap.DisplayName, patchErr, input), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated journey action map %s", d.Id())
return readJourneyActionMap(ctx, d, meta)
}
func deleteJourneyActionMap(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
displayName := d.Get("display_name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
log.Printf("Deleting journey action map with display name %s", displayName)
if resp, err := journeyApi.DeleteJourneyActionmap(d.Id()); err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("failed to delete journey action map with display name %s error: %s", displayName, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := journeyApi.GetJourneyActionmap(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// journey action map deleted
log.Printf("Deleted journey action map %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("error deleting journey action map %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_map", fmt.Sprintf("journey action map %s still exists", d.Id()), resp))
})
}
func flattenActionMap(d *schema.ResourceData, actionMap *platformclientv2.Actionmap) {
d.Set("is_active", *actionMap.IsActive)
d.Set("display_name", *actionMap.DisplayName)
d.Set("trigger_with_segments", lists.StringListToSetOrNil(actionMap.TriggerWithSegments))
resourcedata.SetNillableValue(d, "trigger_with_event_conditions", lists.FlattenList(actionMap.TriggerWithEventConditions, flattenEventCondition))
resourcedata.SetNillableValue(d, "trigger_with_outcome_probability_conditions", lists.FlattenList(actionMap.TriggerWithOutcomeProbabilityConditions, flattenOutcomeProbabilityCondition))
resourcedata.SetNillableValue(d, "page_url_conditions", lists.FlattenList(actionMap.PageUrlConditions, flattenUrlCondition))
d.Set("activation", lists.FlattenAsList(actionMap.Activation, flattenActivation))
d.Set("weight", *actionMap.Weight)
resourcedata.SetNillableValue(d, "action", lists.FlattenAsList(actionMap.Action, flattenActionMapAction))
resourcedata.SetNillableValue(d, "action_map_schedule_groups", lists.FlattenAsList(actionMap.ActionMapScheduleGroups, flattenActionMapScheduleGroups))
d.Set("ignore_frequency_cap", *actionMap.IgnoreFrequencyCap)
resourcedata.SetNillableTime(d, "start_date", actionMap.StartDate)
resourcedata.SetNillableTime(d, "end_date", actionMap.EndDate)
}
func buildSdkActionMap(actionMap *schema.ResourceData) *platformclientv2.Actionmap {
isActive := actionMap.Get("is_active").(bool)
displayName := actionMap.Get("display_name").(string)
triggerWithSegments := lists.BuildSdkStringList(actionMap, "trigger_with_segments")
triggerWithEventConditions := resourcedata.BuildSdkList(actionMap, "trigger_with_event_conditions", buildSdkEventCondition)
triggerWithOutcomeProbabilityConditions := resourcedata.BuildSdkList(actionMap, "trigger_with_outcome_probability_conditions", buildSdkOutcomeProbabilityCondition)
pageUrlConditions := resourcedata.BuildSdkList(actionMap, "page_url_conditions", buildSdkUrlCondition)
activation := resourcedata.BuildSdkListFirstElement(actionMap, "activation", buildSdkActivation, true)
weight := actionMap.Get("weight").(int)
action := resourcedata.BuildSdkListFirstElement(actionMap, "action", buildSdkActionMapAction, true)
actionMapScheduleGroups := resourcedata.BuildSdkListFirstElement(actionMap, "action_map_schedule_groups", buildSdkActionMapScheduleGroups, true)
ignoreFrequencyCap := actionMap.Get("ignore_frequency_cap").(bool)
startDate := resourcedata.GetNillableTime(actionMap, "start_date")
endDate := resourcedata.GetNillableTime(actionMap, "end_date")
return &platformclientv2.Actionmap{
IsActive: &isActive,
DisplayName: &displayName,
TriggerWithSegments: triggerWithSegments,
TriggerWithEventConditions: triggerWithEventConditions,
TriggerWithOutcomeProbabilityConditions: triggerWithOutcomeProbabilityConditions,
PageUrlConditions: pageUrlConditions,
Activation: activation,
Weight: &weight,
Action: action,
ActionMapScheduleGroups: actionMapScheduleGroups,
IgnoreFrequencyCap: &ignoreFrequencyCap,
StartDate: startDate,
EndDate: endDate,
}
}
func buildSdkPatchActionMap(patchActionMap *schema.ResourceData) *platformclientv2.Patchactionmap {
isActive := patchActionMap.Get("is_active").(bool)
displayName := patchActionMap.Get("display_name").(string)
triggerWithSegments := lists.BuildSdkStringList(patchActionMap, "trigger_with_segments")
triggerWithEventConditions := lists.NilToEmptyList(resourcedata.BuildSdkList(patchActionMap, "trigger_with_event_conditions", buildSdkEventCondition))
triggerWithOutcomeProbabilityConditions := lists.NilToEmptyList(resourcedata.BuildSdkList(patchActionMap, "trigger_with_outcome_probability_conditions", buildSdkOutcomeProbabilityCondition))
pageUrlConditions := lists.NilToEmptyList(resourcedata.BuildSdkList(patchActionMap, "page_url_conditions", buildSdkUrlCondition))
activation := resourcedata.BuildSdkListFirstElement(patchActionMap, "activation", buildSdkActivation, true)
weight := patchActionMap.Get("weight").(int)
action := resourcedata.BuildSdkListFirstElement(patchActionMap, "action", buildSdkPatchAction, true)
actionMapScheduleGroups := resourcedata.BuildSdkListFirstElement(patchActionMap, "action_map_schedule_groups", buildSdkPatchActionMapScheduleGroups, true)
ignoreFrequencyCap := patchActionMap.Get("ignore_frequency_cap").(bool)
startDate := resourcedata.GetNillableTime(patchActionMap, "start_date")
endDate := resourcedata.GetNillableTime(patchActionMap, "end_date")
sdkPatchActionMap := platformclientv2.Patchactionmap{}
sdkPatchActionMap.SetField("IsActive", &isActive)
sdkPatchActionMap.SetField("DisplayName", &displayName)
sdkPatchActionMap.SetField("TriggerWithSegments", triggerWithSegments)
sdkPatchActionMap.SetField("TriggerWithEventConditions", triggerWithEventConditions)
sdkPatchActionMap.SetField("TriggerWithOutcomeProbabilityConditions", triggerWithOutcomeProbabilityConditions)
sdkPatchActionMap.SetField("PageUrlConditions", pageUrlConditions)
sdkPatchActionMap.SetField("Activation", activation)
sdkPatchActionMap.SetField("Weight", &weight)
sdkPatchActionMap.SetField("Action", action)
sdkPatchActionMap.SetField("ActionMapScheduleGroups", actionMapScheduleGroups)
sdkPatchActionMap.SetField("IgnoreFrequencyCap", &ignoreFrequencyCap)
sdkPatchActionMap.SetField("StartDate", startDate)
sdkPatchActionMap.SetField("EndDate", endDate)
return &sdkPatchActionMap
}
func flattenEventCondition(eventCondition *platformclientv2.Eventcondition) map[string]interface{} {
eventConditionMap := make(map[string]interface{})
eventConditionMap["key"] = *eventCondition.Key
eventConditionMap["values"] = lists.StringListToSet(*eventCondition.Values)
eventConditionMap["operator"] = *eventCondition.Operator
eventConditionMap["stream_type"] = *eventCondition.StreamType
eventConditionMap["session_type"] = *eventCondition.SessionType
stringmap.SetValueIfNotNil(eventConditionMap, "event_name", eventCondition.EventName)
return eventConditionMap
}
func buildSdkEventCondition(eventCondition map[string]interface{}) *platformclientv2.Eventcondition {
key := eventCondition["key"].(string)
values := stringmap.BuildSdkStringList(eventCondition, "values")
operator := eventCondition["operator"].(string)
streamType := eventCondition["stream_type"].(string)
sessionType := eventCondition["session_type"].(string)
eventName := stringmap.GetNonDefaultValue[string](eventCondition, "event_name")
return &platformclientv2.Eventcondition{
Key: &key,
Values: values,
Operator: &operator,
StreamType: &streamType,
SessionType: &sessionType,
EventName: eventName,
}
}
func flattenOutcomeProbabilityCondition(outcomeProbabilityCondition *platformclientv2.Outcomeprobabilitycondition) map[string]interface{} {
outcomeProbabilityConditionMap := make(map[string]interface{})
outcomeProbabilityConditionMap["outcome_id"] = *outcomeProbabilityCondition.OutcomeId
outcomeProbabilityConditionMap["maximum_probability"] = *typeconv.Float32to64(outcomeProbabilityCondition.MaximumProbability)
stringmap.SetValueIfNotNil(outcomeProbabilityConditionMap, "probability", typeconv.Float32to64(outcomeProbabilityCondition.Probability))
return outcomeProbabilityConditionMap
}
func buildSdkOutcomeProbabilityCondition(outcomeProbabilityCondition map[string]interface{}) *platformclientv2.Outcomeprobabilitycondition {
outcomeId := outcomeProbabilityCondition["outcome_id"].(string)
maximumProbability64 := outcomeProbabilityCondition["maximum_probability"].(float64)
maximumProbability := typeconv.Float64to32(&maximumProbability64)
probability := typeconv.Float64to32(stringmap.GetNonDefaultValue[float64](outcomeProbabilityCondition, "probability"))
return &platformclientv2.Outcomeprobabilitycondition{
OutcomeId: &outcomeId,
MaximumProbability: maximumProbability,
Probability: probability,
}
}
func flattenUrlCondition(urlCondition *platformclientv2.Urlcondition) map[string]interface{} {
urlConditionMap := make(map[string]interface{})
urlConditionMap["values"] = lists.StringListToSet(*urlCondition.Values)
urlConditionMap["operator"] = *urlCondition.Operator
return urlConditionMap
}
func buildSdkUrlCondition(eventCondition map[string]interface{}) *platformclientv2.Urlcondition {
values := stringmap.BuildSdkStringList(eventCondition, "values")
operator := eventCondition["operator"].(string)
return &platformclientv2.Urlcondition{
Values: values,
Operator: &operator,
}
}
func flattenActivation(activation *platformclientv2.Activation) map[string]interface{} {
activationMap := make(map[string]interface{})
activationMap["type"] = *activation.VarType
stringmap.SetValueIfNotNil(activationMap, "delay_in_seconds", activation.DelayInSeconds)
return activationMap
}
func buildSdkActivation(activation map[string]interface{}) *platformclientv2.Activation {
varType := activation["type"].(string)
delayInSeconds := stringmap.GetNonDefaultValue[int](activation, "delay_in_seconds")
return &platformclientv2.Activation{
VarType: &varType,
DelayInSeconds: delayInSeconds,
}
}
func flattenActionMapAction(actionMapAction *platformclientv2.Actionmapaction) map[string]interface{} {
actionMapActionMap := make(map[string]interface{})
actionMapActionMap["media_type"] = *actionMapAction.MediaType
actionMapActionMap["is_pacing_enabled"] = *actionMapAction.IsPacingEnabled
if actionMapAction.ActionTemplate != nil {
stringmap.SetValueIfNotNil(actionMapActionMap, "action_template_id", actionMapAction.ActionTemplate.Id)
}
stringmap.SetValueIfNotNil(actionMapActionMap, "architect_flow_fields", lists.FlattenAsList(actionMapAction.ArchitectFlowFields, flattenArchitectFlowFields))
stringmap.SetValueIfNotNil(actionMapActionMap, "web_messaging_offer_fields", lists.FlattenAsList(actionMapAction.WebMessagingOfferFields, flattenWebMessagingOfferFields))
stringmap.SetValueIfNotNil(actionMapActionMap, "open_action_fields", lists.FlattenAsList(actionMapAction.OpenActionFields, flattenOpenActionFields))
return actionMapActionMap
}
func buildSdkActionMapAction(actionMapAction map[string]interface{}) *platformclientv2.Actionmapaction {
mediaType := actionMapAction["media_type"].(string)
isPacingEnabled := actionMapAction["is_pacing_enabled"].(bool)
actionMapActionTemplate := getActionMapActionTemplate(actionMapAction)
architectFlowFields := stringmap.BuildSdkListFirstElement(actionMapAction, "architect_flow_fields", buildSdkArchitectFlowFields, true)
webMessagingOfferFields := stringmap.BuildSdkListFirstElement(actionMapAction, "web_messaging_offer_fields", buildSdkWebMessagingOfferFields, true)
openActionFields := stringmap.BuildSdkListFirstElement(actionMapAction, "open_action_fields", buildSdkOpenActionFields, true)
return &platformclientv2.Actionmapaction{
MediaType: &mediaType,
IsPacingEnabled: &isPacingEnabled,
ActionTemplate: actionMapActionTemplate,
ArchitectFlowFields: architectFlowFields,
WebMessagingOfferFields: webMessagingOfferFields,
OpenActionFields: openActionFields,
}
}
func buildSdkPatchAction(patchAction map[string]interface{}) *platformclientv2.Patchaction {
mediaType := patchAction["media_type"].(string)
isPacingEnabled := patchAction["is_pacing_enabled"].(bool)
actionMapActionTemplate := getActionMapActionTemplate(patchAction)
architectFlowFields := stringmap.BuildSdkListFirstElement(patchAction, "architect_flow_fields", buildSdkArchitectFlowFields, true)
webMessagingOfferFields := stringmap.BuildSdkListFirstElement(patchAction, "web_messaging_offer_fields", buildSdkPatchWebMessagingOfferFields, true)
openActionFields := stringmap.BuildSdkListFirstElement(patchAction, "open_action_fields", buildSdkOpenActionFields, true)
sdkPatchAction := platformclientv2.Patchaction{}
sdkPatchAction.SetField("MediaType", &mediaType)
sdkPatchAction.SetField("IsPacingEnabled", &isPacingEnabled)
sdkPatchAction.SetField("ActionTemplate", actionMapActionTemplate)
sdkPatchAction.SetField("ArchitectFlowFields", architectFlowFields)
sdkPatchAction.SetField("WebMessagingOfferFields", webMessagingOfferFields)
sdkPatchAction.SetField("OpenActionFields", openActionFields)
return &sdkPatchAction
}
func getActionMapActionTemplate(actionMapAction map[string]interface{}) *platformclientv2.Actionmapactiontemplate {
actionMapActionTemplateId := stringmap.GetNonDefaultValue[string](actionMapAction, "action_template_id")
var actionMapActionTemplate *platformclientv2.Actionmapactiontemplate = nil
if actionMapActionTemplateId != nil {
actionMapActionTemplate = &platformclientv2.Actionmapactiontemplate{
Id: actionMapActionTemplateId,
}
}
return actionMapActionTemplate
}
func flattenArchitectFlowFields(architectFlowFields *platformclientv2.Architectflowfields) map[string]interface{} {
architectFlowFieldsMap := make(map[string]interface{})
architectFlowFieldsMap["architect_flow_id"] = *architectFlowFields.ArchitectFlow.Id
stringmap.SetValueIfNotNil(architectFlowFieldsMap, "flow_request_mappings", lists.FlattenList(architectFlowFields.FlowRequestMappings, flattenRequestMapping))
return architectFlowFieldsMap
}
func buildSdkArchitectFlowFields(architectFlowFields map[string]interface{}) *platformclientv2.Architectflowfields {
architectFlow := getArchitectFlow(architectFlowFields)
flowRequestMappings := stringmap.BuildSdkList(architectFlowFields, "flow_request_mappings", buildSdkRequestMapping)
return &platformclientv2.Architectflowfields{
ArchitectFlow: architectFlow,
FlowRequestMappings: flowRequestMappings,
}
}
func flattenRequestMapping(requestMapping *platformclientv2.Requestmapping) map[string]interface{} {
requestMappingMap := make(map[string]interface{})
requestMappingMap["name"] = *requestMapping.Name
requestMappingMap["attribute_type"] = *requestMapping.AttributeType
requestMappingMap["mapping_type"] = *requestMapping.MappingType
requestMappingMap["value"] = *requestMapping.Value
return requestMappingMap
}
func buildSdkRequestMapping(RequestMapping map[string]interface{}) *platformclientv2.Requestmapping {
name := RequestMapping["name"].(string)
attributeType := RequestMapping["attribute_type"].(string)
mappingType := RequestMapping["mapping_type"].(string)
value := RequestMapping["value"].(string)
return &platformclientv2.Requestmapping{
Name: &name,
AttributeType: &attributeType,
MappingType: &mappingType,
Value: &value,
}
}
func flattenWebMessagingOfferFields(webMessagingOfferFields *platformclientv2.Webmessagingofferfields) map[string]interface{} {
webMessagingOfferFieldsMap := make(map[string]interface{})
if webMessagingOfferFields.OfferText == nil && (webMessagingOfferFields.ArchitectFlow == nil || webMessagingOfferFields.ArchitectFlow.Id == nil) {
return nil
}
stringmap.SetValueIfNotNil(webMessagingOfferFieldsMap, "offer_text", webMessagingOfferFields.OfferText)
if webMessagingOfferFields.ArchitectFlow != nil {
stringmap.SetValueIfNotNil(webMessagingOfferFieldsMap, "architect_flow_id", webMessagingOfferFields.ArchitectFlow.Id)
}
return webMessagingOfferFieldsMap
}
func buildSdkWebMessagingOfferFields(webMessagingOfferFields map[string]interface{}) *platformclientv2.Webmessagingofferfields {
offerText := stringmap.GetNonDefaultValue[string](webMessagingOfferFields, "offer_text")
architectFlow := getArchitectFlow(webMessagingOfferFields)
return &platformclientv2.Webmessagingofferfields{
OfferText: offerText,
ArchitectFlow: architectFlow,
}
}
func buildSdkPatchWebMessagingOfferFields(webMessagingOfferFields map[string]interface{}) *platformclientv2.Patchwebmessagingofferfields {
offerText := stringmap.GetNonDefaultValue[string](webMessagingOfferFields, "offer_text")
architectFlow := getArchitectFlow(webMessagingOfferFields)
return &platformclientv2.Patchwebmessagingofferfields{
OfferText: offerText,
ArchitectFlow: architectFlow,
}
}
func getArchitectFlow(actionMapAction map[string]interface{}) *platformclientv2.Addressableentityref {
architectFlowId := stringmap.GetNonDefaultValue[string](actionMapAction, "architect_flow_id")
var architectFlow *platformclientv2.Addressableentityref = nil
if architectFlowId != nil {
architectFlow = &platformclientv2.Addressableentityref{
Id: architectFlowId,
}
}
return architectFlow
}
func flattenOpenActionFields(openActionFields *platformclientv2.Openactionfields) map[string]interface{} {
architectFlowFieldsMap := make(map[string]interface{})
architectFlowFieldsMap["open_action"] = lists.FlattenAsList(openActionFields.OpenAction, flattenOpenActionDomainEntityRef)
if openActionFields.ConfigurationFields != nil {
jsonString, err := util.InterfaceToJson(openActionFields.ConfigurationFields)
if err != nil {
log.Printf("Error marshalling '%s': %v", "configuration_fields", err)
}
architectFlowFieldsMap["configuration_fields"] = jsonString
}
return architectFlowFieldsMap
}
func buildSdkOpenActionFields(openActionFieldsMap map[string]interface{}) *platformclientv2.Openactionfields {
openAction := stringmap.BuildSdkListFirstElement(openActionFieldsMap, "open_action", buildSdkOpenActionDomainEntityRef, true)
openActionFields := platformclientv2.Openactionfields{
OpenAction: openAction,
}
configurationFieldsString := stringmap.GetNonDefaultValue[string](openActionFieldsMap, "configuration_fields")
if configurationFieldsString != nil {
configurationFieldsInterface, err := util.JsonStringToInterface(*configurationFieldsString)
if err != nil {
log.Printf("Error unmarshalling '%s': %v", "configuration_fields", err)
}
configurationFieldsMap := configurationFieldsInterface.(map[string]interface{})
openActionFields.ConfigurationFields = &configurationFieldsMap
}
return &openActionFields
}
func flattenOpenActionDomainEntityRef(domainEntityRef *platformclientv2.Domainentityref) map[string]interface{} {
domainEntityRefMap := make(map[string]interface{})
domainEntityRefMap["id"] = *domainEntityRef.Id
domainEntityRefMap["name"] = *domainEntityRef.Name
return domainEntityRefMap
}
func buildSdkOpenActionDomainEntityRef(domainEntityRef map[string]interface{}) *platformclientv2.Domainentityref {
id := domainEntityRef["id"].(string)
name := domainEntityRef["name"].(string)
return &platformclientv2.Domainentityref{
Id: &id,
Name: &name,
}
}
func flattenActionMapScheduleGroups(actionMapScheduleGroups *platformclientv2.Actionmapschedulegroups) map[string]interface{} {
actionMapScheduleGroupsMap := make(map[string]interface{})
actionMapScheduleGroupsMap["action_map_schedule_group_id"] = *actionMapScheduleGroups.ActionMapScheduleGroup.Id
if actionMapScheduleGroups.EmergencyActionMapScheduleGroup != nil {
stringmap.SetValueIfNotNil(actionMapScheduleGroupsMap, "emergency_action_map_schedule_group_id", actionMapScheduleGroups.EmergencyActionMapScheduleGroup.Id)
}
return actionMapScheduleGroupsMap
}
func buildSdkActionMapScheduleGroups(actionMapScheduleGroups map[string]interface{}) *platformclientv2.Actionmapschedulegroups {
actionMapScheduleGroup, emergencyActionMapScheduleGroup := getActionMapScheduleGroupPair(actionMapScheduleGroups)
return &platformclientv2.Actionmapschedulegroups{
ActionMapScheduleGroup: actionMapScheduleGroup,
EmergencyActionMapScheduleGroup: emergencyActionMapScheduleGroup,
}
}
func buildSdkPatchActionMapScheduleGroups(actionMapScheduleGroups map[string]interface{}) *platformclientv2.Patchactionmapschedulegroups {
if actionMapScheduleGroups == nil {
return nil
}
actionMapScheduleGroup, emergencyActionMapScheduleGroup := getActionMapScheduleGroupPair(actionMapScheduleGroups)
sdkPatchActionMapScheduleGroups := platformclientv2.Patchactionmapschedulegroups{}
sdkPatchActionMapScheduleGroups.SetField("ActionMapScheduleGroup", actionMapScheduleGroup)
sdkPatchActionMapScheduleGroups.SetField("EmergencyActionMapScheduleGroup", emergencyActionMapScheduleGroup)
return &sdkPatchActionMapScheduleGroups
}
func getActionMapScheduleGroupPair(actionMapScheduleGroups map[string]interface{}) (*platformclientv2.Actionmapschedulegroup, *platformclientv2.Actionmapschedulegroup) {
actionMapScheduleGroupId := actionMapScheduleGroups["action_map_schedule_group_id"].(string)
actionMapScheduleGroup := &platformclientv2.Actionmapschedulegroup{
Id: &actionMapScheduleGroupId,
}
emergencyActionMapScheduleGroupId := stringmap.GetNonDefaultValue[string](actionMapScheduleGroups, "emergency_action_map_schedule_group_id")
var emergencyActionMapScheduleGroup *platformclientv2.Actionmapschedulegroup = nil
if emergencyActionMapScheduleGroupId != nil {
emergencyActionMapScheduleGroup = &platformclientv2.Actionmapschedulegroup{
Id: emergencyActionMapScheduleGroupId,
}
}
return actionMapScheduleGroup, emergencyActionMapScheduleGroup
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"terraform-provider-genesyscloud/genesyscloud/util/stringmap"
"terraform-provider-genesyscloud/genesyscloud/util/typeconv"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
journeyActionTemplateSchema = map[string]*schema.Schema{
"name": {
Description: "Name of the action template.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "Description of the action template's functionality.",
Type: schema.TypeString,
Optional: true,
},
"media_type": {
Description: "The media type of the action configured by the action template.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"webchat", "webMessagingOffer", "contentOffer", "architectFlow", "openAction"}, false),
},
"state": {
Description: "The state of the action template.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Active", "Inactive", "Deleted"}, false),
},
"content_offer": {
Description: "Properties for configuring a content offer action.",
Type: schema.TypeSet,
Optional: true,
Elem: contentOfferResource,
},
}
contentOfferResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"image_url": {
Description: "URL for image displayed on the content offer.",
Type: schema.TypeString,
Optional: true,
},
"display_mode": {
Description: "The display mode used by Genesys Widgets when displaying the content offer.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Modal", "Overlay", "Toast"}, false),
},
"layout_mode": {
Description: "The layout mode used by Genesys Widgets when displaying the content offer.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"TextOnly", "ImageOnly", "LeftText", "RightText", "TopText", "BottomText"}, false),
},
"title": {
Description: "Title in the header of the content offer.",
Type: schema.TypeString,
Optional: true,
},
"headline": {
Description: "Headline displayed above the body text of the content offer.",
Type: schema.TypeString,
Optional: true,
},
"body": {
Description: "Body text of the content offer.",
Type: schema.TypeString,
Optional: true,
},
"call_to_action": {
Description: "Properties customizing the call to action button on the content offer.",
Type: schema.TypeSet,
Optional: true,
Elem: callToActionResource,
},
"style": {
Description: "Properties customizing the styling of the content offer.",
Type: schema.TypeSet,
Optional: true,
Elem: contentOfferStylingConfigurationResource,
},
},
}
callToActionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"text": {
Description: "Text displayed on the call to action button.",
Type: schema.TypeString,
Optional: true,
},
"url": {
Description: "URL to open when user clicks on the call to action button.",
Type: schema.TypeString,
Required: true,
},
"target": {
Description: "Where should the URL be opened when the user clicks on the call to action button.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Blank", "Self"}, false),
},
},
}
contentOfferStylingConfigurationResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"position": {
Description: "Properties for customizing the positioning of the content offer.",
Type: schema.TypeSet,
Optional: true,
Elem: contentPositionPropertiesResource,
},
"offer": {
Description: "Properties for customizing the appearance of the content offer.",
Type: schema.TypeSet,
Optional: true,
Elem: contentOfferStylePropertiesResource,
},
"close_button": {
Description: "Properties for customizing the appearance of the close button.",
Type: schema.TypeSet,
Optional: true,
Elem: closeButtonStylePropertiesResource,
},
"cta_button": {
Description: "Properties for customizing the appearance of the CTA button.",
Type: schema.TypeSet,
Optional: true,
Elem: ctaButtonStylePropertiesResource,
},
"title": {
Description: "Properties for customizing the appearance of the title text.",
Type: schema.TypeSet,
Optional: true,
Elem: textStylePropertiesResource,
},
"headline": {
Description: "Properties for customizing the appearance of the headline text.",
Type: schema.TypeSet,
Optional: true,
Elem: textStylePropertiesResource,
},
"body": {
Description: "Properties for customizing the appearance of the body text.",
Type: schema.TypeSet,
Optional: true,
Elem: textStylePropertiesResource,
},
},
}
contentPositionPropertiesResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"top": {
Description: "Top positioning offset.",
Type: schema.TypeString,
Optional: true,
},
"bottom": {
Description: "Bottom positioning offset.",
Type: schema.TypeString,
Optional: true,
},
"left": {
Description: "Left positioning offset.",
Type: schema.TypeString,
Optional: true,
},
"right": {
Description: "Right positioning offset.",
Type: schema.TypeString,
Optional: true,
},
},
}
contentOfferStylePropertiesResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"padding": {
Description: "Padding of the offer.",
Type: schema.TypeString,
Optional: true,
},
"color": {
Description: "Text color of the offer.",
Type: schema.TypeString,
Optional: true,
},
"background_color": {
Description: "Background color of the offer.",
Type: schema.TypeString,
Optional: true,
},
},
}
closeButtonStylePropertiesResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"color": {
Description: "Color of button.",
Type: schema.TypeString,
Optional: true,
},
"opacity": {
Description: "Opacity of button.",
Type: schema.TypeFloat,
Optional: true,
},
},
}
ctaButtonStylePropertiesResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"color": {
Description: "Color of the text.",
Type: schema.TypeString,
Optional: true,
},
"font": {
Description: "Font of the text.",
Type: schema.TypeString,
Optional: true,
},
"font_size": {
Description: "Font size of the text.",
Type: schema.TypeString,
Optional: true,
},
"text_align": {
Description: "Text alignment.",
Type: schema.TypeString,
Optional: true,
},
"background_color": {
Description: "Background color of the CTA button.",
Type: schema.TypeString,
Optional: true,
},
},
}
textStylePropertiesResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"color": {
Description: "Color of the text.",
Type: schema.TypeString,
Optional: true,
},
"font": {
Description: "Font of the text.",
Type: schema.TypeString,
Optional: true,
},
"font_size": {
Description: "Font size of the text.",
Type: schema.TypeString,
Optional: true,
},
"text_align": {
Description: "Text alignment.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Left", "Right", "Center"}, false),
},
},
}
)
func getAllJourneyActionTemplates(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
journeyApi := platformclientv2.NewJourneyApiWithConfig(clientConfig)
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
actionTemplates, resp, getErr := journeyApi.GetJourneyActiontemplates(pageNum, pageSize, "", "", "", nil, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("Failed to get page of journey action maps error: %s", getErr), resp)
}
if actionTemplates.Entities == nil || len(*actionTemplates.Entities) == 0 {
break
}
for _, actionTemplate := range *actionTemplates.Entities {
resources[*actionTemplate.Id] = &resourceExporter.ResourceMeta{Name: *actionTemplate.Name}
}
pageCount = *actionTemplates.PageCount
}
return resources, nil
}
func JourneyActionTemplateExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllJourneyActionTemplates),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No Reference
}
}
func ResourceJourneyActionTemplate() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Journey Action Template",
CreateContext: provider.CreateWithPooledClient(createJourneyActionTemplate),
ReadContext: provider.ReadWithPooledClient(readJourneyActionTemplate),
UpdateContext: provider.UpdateWithPooledClient(updateJourneyActionTemplate),
DeleteContext: provider.DeleteWithPooledClient(deleteJourneyActionTemplate),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: journeyActionTemplateSchema,
}
}
func createJourneyActionTemplate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
journeyApi := journeyApiConfig(i)
actionTemplate := buildSdkActionTemplate(data)
log.Printf("Creating Journey Action Template %s", *actionTemplate.Name)
result, resp, err := journeyApi.PostJourneyActiontemplates(*actionTemplate)
if err != nil {
input, _ := util.InterfaceToJson(*actionTemplate)
return util.BuildAPIDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("Failed to create journey action template %s (input: %+v) error: %s", *actionTemplate.Name, input, err), resp)
}
data.SetId(*result.Id)
log.Printf("Created Journey Action Template %s %s", *result.Name, *result.Id)
return readJourneyActionTemplate(ctx, data, i)
}
func readJourneyActionTemplate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
journeyApi := journeyApiConfig(i)
cc := consistency_checker.NewConsistencyCheck(ctx, data, i, ResourceJourneyActionTemplate(), constants.DefaultConsistencyChecks, "genesyscloud_journey_action_template")
log.Printf("Reading Journey Action Template %s", data.Id())
return util.WithRetriesForRead(ctx, data, func() *retry.RetryError {
actionTemplate, resp, getErr := journeyApi.GetJourneyActiontemplate(data.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("failed to read Journey Action Template %s | error: %s", data.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("failed to read Journey Action Template %s | error: %s", data.Id(), getErr), resp))
}
flattenActionTemplate(data, actionTemplate)
log.Printf("Read Journey Action Template %s %s", data.Id(), *actionTemplate.Name)
return cc.CheckState(data)
})
}
func updateJourneyActionTemplate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
journeyApi := journeyApiConfig(i)
patchActionTemplate := buildSdkPatchActionTemplate(data)
log.Printf("Updating Journey Action Template %s", data.Id())
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
actionTemplate, resp, getErr := journeyApi.GetJourneyActiontemplate(data.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("failed to read journey action template %s error: %s", data.Id(), getErr), resp)
}
patchActionTemplate.Version = actionTemplate.Version
_, resp, patchErr := journeyApi.PatchJourneyActiontemplate(data.Id(), *patchActionTemplate)
if patchErr != nil {
input, _ := util.InterfaceToJson(*patchActionTemplate)
return resp, util.BuildAPIDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("failed to update journey action template %s (input: %+v) error: %s", *actionTemplate.Name, input, patchErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Journey Action Template %s", data.Id())
return readJourneyActionTemplate(ctx, data, i)
}
func deleteJourneyActionTemplate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
journeyApi := journeyApiConfig(i)
name := data.Get("name").(string)
log.Printf("Deleting Journey Action Template with name %s", name)
if resp, err := journeyApi.DeleteJourneyActiontemplate(data.Id(), true); err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("create journey action template %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := journeyApi.GetJourneyActiontemplate(data.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted Journey Action Template %s", data.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("error deleting journey action template %s | error: %s", data.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_action_template", fmt.Sprintf("journey action template %s still exists", data.Id()), resp))
})
}
func journeyApiConfig(meta interface{}) *platformclientv2.JourneyApi {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
return journeyApi
}
// All buildSdkPatch* functions are helper method which maps Create operation of journeyApi's Actiontemplates
func buildSdkPatchActionTemplate(patchActionTemplate *schema.ResourceData) *platformclientv2.Patchactiontemplate {
name := patchActionTemplate.Get("name").(string)
description := patchActionTemplate.Get("description").(string)
mediaType := patchActionTemplate.Get("media_type").(string)
state := patchActionTemplate.Get("state").(string)
contentOffer := resourcedata.BuildSdkListFirstElement(patchActionTemplate, "content_offer", buildSdkPatchContentOffer, true)
sdkPatchActionTemplate := platformclientv2.Patchactiontemplate{}
sdkPatchActionTemplate.SetField("Name", &name)
sdkPatchActionTemplate.SetField("Description", &description)
sdkPatchActionTemplate.SetField("MediaType", &mediaType)
sdkPatchActionTemplate.SetField("State", &state)
sdkPatchActionTemplate.SetField("ContentOffer", contentOffer)
return &sdkPatchActionTemplate
}
func buildSdkPatchContentOffer(patchContentOffer map[string]interface{}) *platformclientv2.Patchcontentoffer {
imageUrl := patchContentOffer["image_url"].(string)
displayMode := patchContentOffer["display_mode"].(string)
layoutMode := patchContentOffer["layout_mode"].(string)
title := patchContentOffer["title"].(string)
headline := patchContentOffer["headline"].(string)
body := patchContentOffer["body"].(string)
callToAction := stringmap.BuildSdkListFirstElement(patchContentOffer, "call_to_action", buildSdkPatchCallToAction, true)
style := stringmap.BuildSdkListFirstElement(patchContentOffer, "style", buildSdkPatchContentOfferStylingConfiguration, true)
sdkPatchActionTemplate := platformclientv2.Patchcontentoffer{}
sdkPatchActionTemplate.SetField("ImageUrl", &imageUrl)
sdkPatchActionTemplate.SetField("DisplayMode", &displayMode)
sdkPatchActionTemplate.SetField("LayoutMode", &layoutMode)
sdkPatchActionTemplate.SetField("Title", &title)
sdkPatchActionTemplate.SetField("Headline", &headline)
sdkPatchActionTemplate.SetField("Body", &body)
sdkPatchActionTemplate.SetField("CallToAction", callToAction)
sdkPatchActionTemplate.SetField("Style", style)
return &sdkPatchActionTemplate
}
func buildSdkPatchContentOfferStylingConfiguration(patchContentOfferStyle map[string]interface{}) *platformclientv2.Patchcontentofferstylingconfiguration {
position := stringmap.BuildSdkListFirstElement(patchContentOfferStyle, "position", buildSdkPatchPosition, true)
offer := stringmap.BuildSdkListFirstElement(patchContentOfferStyle, "offer", buildSdkPatchOffer, true)
closeButton := stringmap.BuildSdkListFirstElement(patchContentOfferStyle, "close_button", buildSdkPatchCloseButton, true)
ctaButton := stringmap.BuildSdkListFirstElement(patchContentOfferStyle, "cta_button", buildSdkPatchCtaButton, true)
title := stringmap.BuildSdkListFirstElement(patchContentOfferStyle, "title", buildSdkPatchTitleOrHeadlineOrBody, true)
headline := stringmap.BuildSdkListFirstElement(patchContentOfferStyle, "headline", buildSdkPatchTitleOrHeadlineOrBody, true)
body := stringmap.BuildSdkListFirstElement(patchContentOfferStyle, "body", buildSdkPatchTitleOrHeadlineOrBody, true)
sdkPatchContentOfferStyle := platformclientv2.Patchcontentofferstylingconfiguration{}
sdkPatchContentOfferStyle.SetField("Position", position)
sdkPatchContentOfferStyle.SetField("Offer", offer)
sdkPatchContentOfferStyle.SetField("CloseButton", closeButton)
sdkPatchContentOfferStyle.SetField("CtaButton", ctaButton)
sdkPatchContentOfferStyle.SetField("Title", title)
sdkPatchContentOfferStyle.SetField("Headline", headline)
sdkPatchContentOfferStyle.SetField("Body", body)
return &sdkPatchContentOfferStyle
}
func buildSdkPatchPosition(patchContentPositionProp map[string]interface{}) *platformclientv2.Patchcontentpositionproperties {
top := patchContentPositionProp["top"].(string)
bottom := patchContentPositionProp["bottom"].(string)
left := patchContentPositionProp["left"].(string)
right := patchContentPositionProp["right"].(string)
sdkPatchContentPositionProp := &platformclientv2.Patchcontentpositionproperties{}
sdkPatchContentPositionProp.SetField("Top", &top)
sdkPatchContentPositionProp.SetField("Bottom", &bottom)
sdkPatchContentPositionProp.SetField("Left", &left)
sdkPatchContentPositionProp.SetField("Right", &right)
return sdkPatchContentPositionProp
}
func buildSdkPatchOffer(patchContentOfferStyleProp map[string]interface{}) *platformclientv2.Patchcontentofferstyleproperties {
padding := patchContentOfferStyleProp["padding"].(string)
color := patchContentOfferStyleProp["color"].(string)
backgroundColor := patchContentOfferStyleProp["background_color"].(string)
sdkPatchContentOfferStyleProp := &platformclientv2.Patchcontentofferstyleproperties{}
sdkPatchContentOfferStyleProp.SetField("Padding", &padding)
sdkPatchContentOfferStyleProp.SetField("Color", &color)
sdkPatchContentOfferStyleProp.SetField("BackgroundColor", &backgroundColor)
return sdkPatchContentOfferStyleProp
}
func buildSdkPatchCloseButton(patchCloseButtonStyleProp map[string]interface{}) *platformclientv2.Patchclosebuttonstyleproperties {
color := patchCloseButtonStyleProp["color"].(string)
opacity64 := patchCloseButtonStyleProp["opacity"].(float64)
opacity := typeconv.Float64to32(&opacity64)
skdPatchCloseButtonStyleProp := &platformclientv2.Patchclosebuttonstyleproperties{}
skdPatchCloseButtonStyleProp.SetField("Color", &color)
skdPatchCloseButtonStyleProp.SetField("Opacity", opacity)
return skdPatchCloseButtonStyleProp
}
func buildSdkPatchCtaButton(patchCtaButtonStyleProp map[string]interface{}) *platformclientv2.Patchctabuttonstyleproperties {
color := patchCtaButtonStyleProp["color"].(string)
font := patchCtaButtonStyleProp["font"].(string)
fontSize := patchCtaButtonStyleProp["font_size"].(string)
textAlign := patchCtaButtonStyleProp["text_align"].(string)
backgroundColor := patchCtaButtonStyleProp["background_color"].(string)
sdkPatchCtaButtonStyleProp := &platformclientv2.Patchctabuttonstyleproperties{}
sdkPatchCtaButtonStyleProp.SetField("Color", &color)
sdkPatchCtaButtonStyleProp.SetField("Font", &font)
sdkPatchCtaButtonStyleProp.SetField("FontSize", &fontSize)
sdkPatchCtaButtonStyleProp.SetField("TextAlign", &textAlign)
sdkPatchCtaButtonStyleProp.SetField("BackgroundColor", &backgroundColor)
return sdkPatchCtaButtonStyleProp
}
func buildSdkPatchTitleOrHeadlineOrBody(patchTextStyleProp map[string]interface{}) *platformclientv2.Patchtextstyleproperties {
color := patchTextStyleProp["color"].(string)
font := patchTextStyleProp["font"].(string)
fontSize := patchTextStyleProp["font_size"].(string)
textAlign := patchTextStyleProp["text_align"].(string)
sdkPatchTextStyleProp := &platformclientv2.Patchtextstyleproperties{}
sdkPatchTextStyleProp.SetField("Color", &color)
sdkPatchTextStyleProp.SetField("Font", &font)
sdkPatchTextStyleProp.SetField("FontSize", &fontSize)
sdkPatchTextStyleProp.SetField("TextAlign", &textAlign)
return sdkPatchTextStyleProp
}
func buildSdkPatchCallToAction(patchCallToAction map[string]interface{}) *platformclientv2.Patchcalltoaction {
text := patchCallToAction["text"].(string)
url := patchCallToAction["url"].(string)
targetUrl := patchCallToAction["target"].(string)
sdkPatchCallToAction := &platformclientv2.Patchcalltoaction{}
sdkPatchCallToAction.SetField("Text", &text)
sdkPatchCallToAction.SetField("Url", &url)
sdkPatchCallToAction.SetField("Target", &targetUrl)
return sdkPatchCallToAction
}
// All buildSdk* (not buildSdkPatch*) functions are helper method which maps Create operation of journeyApi's Actiontemplates
func buildSdkActionTemplate(actionTemplate *schema.ResourceData) *platformclientv2.Actiontemplate {
name := actionTemplate.Get("name").(string)
description := actionTemplate.Get("description").(string)
mediaType := actionTemplate.Get("media_type").(string)
state := actionTemplate.Get("state").(string)
contentOffer := resourcedata.BuildSdkListFirstElement(actionTemplate, "content_offer", buildSdkContentOffer, true)
return &platformclientv2.Actiontemplate{
Name: &name,
Description: &description,
MediaType: &mediaType,
State: &state,
ContentOffer: contentOffer,
}
}
func buildSdkContentOffer(contentOffer map[string]interface{}) *platformclientv2.Contentoffer {
imageUrl := contentOffer["image_url"].(string)
displayMode := contentOffer["display_mode"].(string)
layoutMode := contentOffer["layout_mode"].(string)
title := contentOffer["title"].(string)
headline := contentOffer["headline"].(string)
body := contentOffer["body"].(string)
callToAction := stringmap.BuildSdkListFirstElement(contentOffer, "call_to_action", buildSdkCallToAction, true)
style := stringmap.BuildSdkListFirstElement(contentOffer, "style", buildSdkContentOfferStylingConfiguration, true)
return &platformclientv2.Contentoffer{
ImageUrl: &imageUrl,
DisplayMode: &displayMode,
LayoutMode: &layoutMode,
Title: &title,
Headline: &headline,
Body: &body,
CallToAction: callToAction,
Style: style,
}
}
func buildSdkCallToAction(callToAction map[string]interface{}) *platformclientv2.Calltoaction {
text := callToAction["text"].(string)
url := callToAction["url"].(string)
targetUrl := callToAction["target"].(string)
return &platformclientv2.Calltoaction{
Text: &text,
Url: &url,
Target: &targetUrl,
}
}
func buildSdkContentOfferStylingConfiguration(contentOfferStylingConfig map[string]interface{}) *platformclientv2.Contentofferstylingconfiguration {
position := stringmap.BuildSdkListFirstElement(contentOfferStylingConfig, "position", buildSdkContentPositionProperties, true)
offer := stringmap.BuildSdkListFirstElement(contentOfferStylingConfig, "offer", buildSdkContentOfferStyleProperties, true)
closeButton := stringmap.BuildSdkListFirstElement(contentOfferStylingConfig, "close_button", buildSdkCloseButtonStyleProperties, true)
ctaButton := stringmap.BuildSdkListFirstElement(contentOfferStylingConfig, "cta_button", buildSdkCtaButtonStyleProperties, true)
title := stringmap.BuildSdkListFirstElement(contentOfferStylingConfig, "title", buildSdkTextStyleProperties, true)
headline := stringmap.BuildSdkListFirstElement(contentOfferStylingConfig, "headline", buildSdkTextStyleProperties, true)
body := stringmap.BuildSdkListFirstElement(contentOfferStylingConfig, "body", buildSdkTextStyleProperties, true)
return &platformclientv2.Contentofferstylingconfiguration{
Position: position,
Offer: offer,
CloseButton: closeButton,
CtaButton: ctaButton,
Title: title,
Headline: headline,
Body: body,
}
}
func buildSdkContentPositionProperties(contentPositionProperties map[string]interface{}) *platformclientv2.Contentpositionproperties {
top := contentPositionProperties["top"].(string)
bottom := contentPositionProperties["bottom"].(string)
left := contentPositionProperties["left"].(string)
right := contentPositionProperties["right"].(string)
return &platformclientv2.Contentpositionproperties{
Top: &top,
Bottom: &bottom,
Left: &left,
Right: &right,
}
}
func buildSdkContentOfferStyleProperties(contentPositionProperties map[string]interface{}) *platformclientv2.Contentofferstyleproperties {
padding := contentPositionProperties["padding"].(string)
color := contentPositionProperties["color"].(string)
backGroundColor := contentPositionProperties["background_color"].(string)
return &platformclientv2.Contentofferstyleproperties{
Padding: &padding,
Color: &color,
BackgroundColor: &backGroundColor,
}
}
func buildSdkCtaButtonStyleProperties(contentPositionProperties map[string]interface{}) *platformclientv2.Ctabuttonstyleproperties {
color := contentPositionProperties["color"].(string)
font := contentPositionProperties["font"].(string)
fontSize := contentPositionProperties["font_size"].(string)
textAlign := contentPositionProperties["text_align"].(string)
backgoundColor := contentPositionProperties["background_color"].(string)
return &platformclientv2.Ctabuttonstyleproperties{
Color: &color,
Font: &font,
FontSize: &fontSize,
TextAlign: &textAlign,
BackgroundColor: &backgoundColor,
}
}
func buildSdkCloseButtonStyleProperties(contentPositionProperties map[string]interface{}) *platformclientv2.Closebuttonstyleproperties {
color := contentPositionProperties["color"].(string)
opacity64 := contentPositionProperties["opacity"].(float64)
opacity := typeconv.Float64to32(&opacity64)
return &platformclientv2.Closebuttonstyleproperties{
Color: &color,
Opacity: opacity,
}
}
func buildSdkTextStyleProperties(contentPositionProperties map[string]interface{}) *platformclientv2.Textstyleproperties {
color := contentPositionProperties["color"].(string)
font := contentPositionProperties["font"].(string)
fontSize := contentPositionProperties["font_size"].(string)
textAlign := contentPositionProperties["text_align"].(string)
return &platformclientv2.Textstyleproperties{
Color: &color,
Font: &font,
FontSize: &fontSize,
TextAlign: &textAlign,
}
}
// All flatten* functions are helper method which maps Read operation of journeyApi's Actiontemplates
func flattenActionTemplate(data *schema.ResourceData, actionTemplate *platformclientv2.Actiontemplate) {
data.Set("name", *actionTemplate.Name)
resourcedata.SetNillableValue(data, "description", actionTemplate.Description)
data.Set("media_type", *actionTemplate.MediaType)
data.Set("state", *actionTemplate.State)
resourcedata.SetNillableValue(data, "content_offer", lists.FlattenAsList(actionTemplate.ContentOffer, flattenActionTemplateContentOffer))
}
func flattenActionTemplateContentOffer(resource *platformclientv2.Contentoffer) map[string]interface{} {
actionTemplateContentOfferMap := make(map[string]interface{})
actionTemplateContentOfferMap["image_url"] = resource.ImageUrl
actionTemplateContentOfferMap["display_mode"] = resource.DisplayMode
actionTemplateContentOfferMap["layout_mode"] = resource.LayoutMode
actionTemplateContentOfferMap["title"] = resource.Title
actionTemplateContentOfferMap["headline"] = resource.Headline
actionTemplateContentOfferMap["body"] = resource.Body
stringmap.SetValueIfNotNil(actionTemplateContentOfferMap, "call_to_action", lists.FlattenAsList(resource.CallToAction, flattenCallToAction))
stringmap.SetValueIfNotNil(actionTemplateContentOfferMap, "style", lists.FlattenAsList(resource.Style, flattenStyle))
return actionTemplateContentOfferMap
}
func flattenCallToAction(resource *platformclientv2.Calltoaction) map[string]interface{} {
callToActionMap := make(map[string]interface{})
callToActionMap["text"] = resource.Text
callToActionMap["url"] = resource.Url
callToActionMap["target"] = resource.Target
return callToActionMap
}
func flattenStyle(resource *platformclientv2.Contentofferstylingconfiguration) map[string]interface{} {
styleMap := make(map[string]interface{})
stringmap.SetValueIfNotNil(styleMap, "position", lists.FlattenAsList(resource.Position, flattenPositionProperties))
stringmap.SetValueIfNotNil(styleMap, "offer", lists.FlattenAsList(resource.Offer, flattenOfferProperties))
stringmap.SetValueIfNotNil(styleMap, "close_button", lists.FlattenAsList(resource.CloseButton, flattenCloseButtonProperties))
stringmap.SetValueIfNotNil(styleMap, "cta_button", lists.FlattenAsList(resource.CtaButton, flattenCtaButtonProperties))
stringmap.SetValueIfNotNil(styleMap, "title", lists.FlattenAsList(resource.Title, flattenTextStyleProperties))
stringmap.SetValueIfNotNil(styleMap, "headline", lists.FlattenAsList(resource.Headline, flattenTextStyleProperties))
stringmap.SetValueIfNotNil(styleMap, "body", lists.FlattenAsList(resource.Body, flattenTextStyleProperties))
return styleMap
}
func flattenPositionProperties(resource *platformclientv2.Contentpositionproperties) map[string]interface{} {
positionMap := make(map[string]interface{})
positionMap["top"] = resource.Top
positionMap["bottom"] = resource.Bottom
positionMap["left"] = resource.Left
positionMap["right"] = resource.Right
return positionMap
}
func flattenOfferProperties(resource *platformclientv2.Contentofferstyleproperties) map[string]interface{} {
offerMap := make(map[string]interface{})
offerMap["padding"] = resource.Padding
offerMap["color"] = resource.Color
offerMap["background_color"] = resource.BackgroundColor
return offerMap
}
func flattenCloseButtonProperties(resource *platformclientv2.Closebuttonstyleproperties) map[string]interface{} {
closeButtonMap := make(map[string]interface{})
closeButtonMap["color"] = resource.Color
closeButtonMap["opacity"] = *typeconv.Float32to64(resource.Opacity)
return closeButtonMap
}
func flattenCtaButtonProperties(resource *platformclientv2.Ctabuttonstyleproperties) map[string]interface{} {
ctaButtonMap := make(map[string]interface{})
ctaButtonMap["color"] = resource.Color
ctaButtonMap["font"] = resource.Font
ctaButtonMap["font_size"] = resource.FontSize
ctaButtonMap["text_align"] = resource.TextAlign
ctaButtonMap["background_color"] = resource.BackgroundColor
return ctaButtonMap
}
func flattenTextStyleProperties(resource *platformclientv2.Textstyleproperties) map[string]interface{} {
textStyleMap := make(map[string]interface{})
textStyleMap["color"] = resource.Color
textStyleMap["font"] = resource.Font
textStyleMap["font_size"] = resource.FontSize
textStyleMap["text_align"] = resource.TextAlign
return textStyleMap
}
package genesyscloud
import (
"context"
"fmt"
"log"
"regexp"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
journeyOutcomeSchema = map[string]*schema.Schema{
"is_active": {
Description: "Whether or not the outcome is active.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"display_name": {
Description: "The display name of the outcome.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "A description of the outcome.",
Type: schema.TypeString,
Optional: true,
},
"is_positive": {
Description: "Whether or not the outcome is positive.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"context": {
Description: "The context of the outcome.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: contextResource,
},
"journey": {
Description: "The pattern of rules defining the outcome.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: journeyResource,
},
"associated_value_field": {
Description: " The field from the event indicating the associated value. Associated_value_field needs `eventtypes` to be created, which is a feature coming soon. More details available here: https://developer.genesys.cloud/commdigital/digital/webmessaging/journey/eventtypes https://all.docs.genesys.com/ATC/Current/AdminGuide/Custom_sessions",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: associatedValueFieldResource,
},
}
associatedValueFieldResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"data_type": {
Description: "The data type of the value field.Valid values: Number, Integer.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Number", "Integer"}, false),
},
"name": {
Description: "The field name for extracting value from event.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringMatch(func() *regexp.Regexp {
r, _ := regexp.Compile("^attributes\\..+\\.value$")
return r
}(), ""),
},
},
}
)
func getAllJourneyOutcomes(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
journeyApi := platformclientv2.NewJourneyApiWithConfig(clientConfig)
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
journeyOutcomes, resp, getErr := journeyApi.GetJourneyOutcomes(pageNum, pageSize, "", nil, nil, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("Failed to get page of journey outcomes error: %s", getErr), resp)
}
if journeyOutcomes.Entities == nil || len(*journeyOutcomes.Entities) == 0 {
break
}
for _, journeyOutcome := range *journeyOutcomes.Entities {
resources[*journeyOutcome.Id] = &resourceExporter.ResourceMeta{Name: *journeyOutcome.DisplayName}
}
pageCount = *journeyOutcomes.PageCount
}
return resources, nil
}
func JourneyOutcomeExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllJourneyOutcomes),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceJourneyOutcome() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Journey Outcome",
CreateContext: provider.CreateWithPooledClient(createJourneyOutcome),
ReadContext: provider.ReadWithPooledClient(readJourneyOutcome),
UpdateContext: provider.UpdateWithPooledClient(updateJourneyOutcome),
DeleteContext: provider.DeleteWithPooledClient(deleteJourneyOutcome),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: journeyOutcomeSchema,
}
}
func createJourneyOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
journeyOutcome := buildSdkJourneyOutcome(d)
log.Printf("Creating journey outcome %s", *journeyOutcome.DisplayName)
result, resp, err := journeyApi.PostJourneyOutcomes(*journeyOutcome)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("Failed to create journey outcome %s error: %s", *journeyOutcome.DisplayName, err), resp)
}
d.SetId(*result.Id)
log.Printf("Created journey outcome %s %s", *result.DisplayName, *result.Id)
return readJourneyOutcome(ctx, d, meta)
}
func readJourneyOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceJourneyOutcome(), constants.DefaultConsistencyChecks, "genesyscloud_journey_outcome")
log.Printf("Reading journey outcome %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
journeyOutcome, resp, getErr := journeyApi.GetJourneyOutcome(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("failed to read journey outcome %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("failed to read journey outcome %s | error: %s", d.Id(), getErr), resp))
}
flattenJourneyOutcome(d, journeyOutcome)
log.Printf("Read journey outcome %s %s", d.Id(), *journeyOutcome.DisplayName)
return cc.CheckState(d)
})
}
func updateJourneyOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
patchOutcome := buildSdkPatchOutcome(d)
log.Printf("Updating journey outcome %s", d.Id())
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current journey outcome version
journeyOutcome, resp, getErr := journeyApi.GetJourneyOutcome(d.Id())
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("Failed to read journey outcome %s error: %s", d.Id(), getErr), resp)
}
patchOutcome.Version = journeyOutcome.Version
_, resp, patchErr := journeyApi.PatchJourneyOutcome(d.Id(), *patchOutcome)
if patchErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("Failed to update journey outcome %s error: %s", *patchOutcome.DisplayName, patchErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated journey outcome %s", d.Id())
return readJourneyOutcome(ctx, d, meta)
}
func deleteJourneyOutcome(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
displayName := d.Get("display_name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
log.Printf("Deleting journey outcome with display name %s", displayName)
if resp, err := journeyApi.DeleteJourneyOutcome(d.Id()); err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("Failed to delete journey outcome %s error: %s", displayName, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := journeyApi.GetJourneyOutcome(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// journey outcome deleted
log.Printf("Deleted journey outcome %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("error deleting journey outcome %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_outcome", fmt.Sprintf("journey outcome %s still exists", d.Id()), resp))
})
}
func flattenJourneyOutcome(d *schema.ResourceData, journeyOutcome *platformclientv2.Outcome) {
d.Set("is_active", *journeyOutcome.IsActive)
d.Set("display_name", *journeyOutcome.DisplayName)
resourcedata.SetNillableValue(d, "description", journeyOutcome.Description)
resourcedata.SetNillableValue(d, "is_positive", journeyOutcome.IsPositive)
resourcedata.SetNillableValue(d, "context", lists.FlattenAsList(journeyOutcome.Context, flattenContext))
resourcedata.SetNillableValue(d, "journey", lists.FlattenAsList(journeyOutcome.Journey, flattenJourney))
resourcedata.SetNillableValue(d, "associated_value_field", lists.FlattenAsList(journeyOutcome.AssociatedValueField, flattenAssociatedValueField))
}
func flattenAssociatedValueField(associatedValueField *platformclientv2.Associatedvaluefield) map[string]interface{} {
associatedValueFieldMap := make(map[string]interface{})
associatedValueFieldMap["data_type"] = associatedValueField.DataType
associatedValueFieldMap["name"] = associatedValueField.Name
return associatedValueFieldMap
}
func buildSdkJourneyOutcome(journeyOutcome *schema.ResourceData) *platformclientv2.Outcomerequest {
isActive := journeyOutcome.Get("is_active").(bool)
displayName := journeyOutcome.Get("display_name").(string)
description := resourcedata.GetNillableValue[string](journeyOutcome, "description")
isPositive := resourcedata.GetNillableBool(journeyOutcome, "is_positive")
sdkContext := resourcedata.BuildSdkListFirstElement(journeyOutcome, "context", buildSdkRequestContext, false)
journey := resourcedata.BuildSdkListFirstElement(journeyOutcome, "journey", buildSdkRequestJourney, false)
associatedValueField := resourcedata.BuildSdkListFirstElement(journeyOutcome, "associated_value_field", buildSdkAssociatedValueField, true)
return &platformclientv2.Outcomerequest{
IsActive: &isActive,
DisplayName: &displayName,
Description: description,
IsPositive: isPositive,
Context: sdkContext,
Journey: journey,
AssociatedValueField: associatedValueField,
}
}
func buildSdkPatchOutcome(journeyOutcome *schema.ResourceData) *platformclientv2.Patchoutcome {
isActive := journeyOutcome.Get("is_active").(bool)
displayName := journeyOutcome.Get("display_name").(string)
description := resourcedata.GetNillableValue[string](journeyOutcome, "description")
isPositive := resourcedata.GetNillableBool(journeyOutcome, "is_positive")
sdkContext := resourcedata.BuildSdkListFirstElement(journeyOutcome, "context", buildSdkPatchContext, false)
journey := resourcedata.BuildSdkListFirstElement(journeyOutcome, "journey", buildSdkPatchJourney, false)
return &platformclientv2.Patchoutcome{
IsActive: &isActive,
DisplayName: &displayName,
Description: description,
IsPositive: isPositive,
Context: sdkContext,
Journey: journey,
}
}
func buildSdkAssociatedValueField(associatedValueField map[string]interface{}) *platformclientv2.Associatedvaluefield {
dataType := associatedValueField["data_type"].(string)
name := associatedValueField["name"].(string)
return &platformclientv2.Associatedvaluefield{
DataType: &dataType,
Name: &name,
}
}
package genesyscloud
import (
"context"
"fmt"
"log"
"regexp"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"terraform-provider-genesyscloud/genesyscloud/util/stringmap"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
journeySegmentSchema = map[string]*schema.Schema{
"is_active": {
Description: "Whether or not the segment is active.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"display_name": {
Description: "The display name of the segment.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "A description of the segment.",
Type: schema.TypeString,
Optional: true,
},
"color": {
Description: "The hexadecimal color value of the segment.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringMatch(func() *regexp.Regexp {
r, _ := regexp.Compile("^#[a-fA-F\\d]{6}$")
return r
}(), ""),
},
"scope": {
Description: "The target entity that a segment applies to.Valid values: Session, Customer. Changing the scope attribute will cause the existing journey_segment to be dropped and recreated with new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true, // scope can be only set during creation
ValidateFunc: validation.StringInSlice([]string{"Session", "Customer"}, false),
},
"should_display_to_agent": {
Description: "Whether or not the segment should be displayed to agent/supervisor users.",
Type: schema.TypeBool,
Optional: true,
// Customer scope only supports false for this value
},
"context": {
Description: "The context of the segment.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: contextResource,
},
"journey": {
Description: "The pattern of rules defining the segment.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: journeyResource,
},
"external_segment": {
Description: "Details of an entity corresponding to this segment in an external system.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: externalSegmentResource, // can only be used with Customer scope
},
"assignment_expiration_days": {
Description: "Time, in days, from when the segment is assigned until it is automatically unassigned.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(2, 60),
},
}
contextResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"patterns": {
Description: "A list of one or more patterns to match.",
Type: schema.TypeSet,
Required: true,
Elem: contextPatternResource,
},
},
}
contextPatternResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"criteria": {
Description: "A list of one or more criteria to satisfy.",
Type: schema.TypeSet,
Required: true,
Elem: entityTypeCriteriaResource,
},
},
}
entityTypeCriteriaResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Description: "The criteria key.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"device.category", "device.type", "device.osFamily", "browser.family", "browser.lang", "browser.version", "mktCampaign.source", "mktCampaign.medium", "mktCampaign.name", "mktCampaign.term", "mktCampaign.content", "mktCampaign.clickId", "mktCampaign.network", "geolocation.countryName", "geolocation.locality", "geolocation.region", "geolocation.postalCode", "geolocation.country", "ipOrganization", "referrer.url", "referrer.medium", "referrer.hostname", "authenticated"}, false),
},
"values": {
Description: "The criteria values.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"should_ignore_case": {
Description: "Should criteria be case insensitive.",
Type: schema.TypeBool,
Required: true,
},
"operator": {
Description: "The comparison operator. Valid values: containsAll, containsAny, notContainsAll, notContainsAny, equal, notEqual, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, startsWith, endsWith.",
Type: schema.TypeString,
Optional: true,
Default: "equal",
ValidateFunc: validation.StringInSlice([]string{"containsAll", "containsAny", "notContainsAll", "notContainsAny", "equal", "notEqual", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "startsWith", "endsWith"}, false),
},
"entity_type": {
Description: "The entity to match the pattern against.Valid values: visit.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"visit"}, false),
},
},
}
journeyResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"patterns": {
Description: "A list of zero or more patterns to match.",
Type: schema.TypeSet,
Required: true,
Elem: journeyPatternResource,
},
},
}
journeyPatternResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"criteria": {
Description: "A list of one or more criteria to satisfy.",
Type: schema.TypeSet,
Required: true,
Elem: criteriaResource,
},
"count": {
Description: "The number of times the pattern must match.",
Type: schema.TypeInt,
Required: true,
},
"stream_type": {
Description: "The stream type for which this pattern can be matched on.Valid values: Web, Custom, Conversation.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Web" /*, "Custom", "Conversation"*/}, false), // Custom and Conversation seem not to be supported by the API despite the documentation
},
"session_type": {
Description: "The session type for which this pattern can be matched on.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"web"}, false), // custom value seems not to be supported by the API despite the documentation
},
"event_name": {
Description: "The name of the event for which this pattern can be matched on.",
Type: schema.TypeString,
Optional: true,
Default: nil,
},
},
}
criteriaResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Description: "The criteria key.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.Any(
validation.StringInSlice([]string{"eventName", "page.url", "page.title", "page.hostname", "page.domain", "page.fragment", "page.keywords", "page.pathname", "searchQuery", "page.queryString"}, false),
validation.StringMatch(func() *regexp.Regexp {
r, _ := regexp.Compile("attributes\\..*\\.value")
return r
}(), ""),
),
},
"values": {
Description: "The criteria values.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"should_ignore_case": {
Description: "Should criteria be case insensitive.",
Type: schema.TypeBool,
Required: true,
},
"operator": {
Description: "The comparison operator.Valid values: containsAll, containsAny, notContainsAll, notContainsAny, equal, notEqual, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, startsWith, endsWith.",
Type: schema.TypeString,
Optional: true,
Default: "equal",
ValidateFunc: validation.StringInSlice([]string{"containsAll", "containsAny", "notContainsAll", "notContainsAny", "equal", "notEqual", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "startsWith", "endsWith"}, false),
},
},
}
externalSegmentResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Description: "Identifier for the external segment in the system where it originates from. Changing the id attribute will cause the journey_segment resource to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Description: "Name for the external segment in the system where it originates from.",
Type: schema.TypeString,
Required: true,
},
"source": {
Description: "The external system where the segment originates from.Valid values: AdobeExperiencePlatform, Custom. Changing the source attribute will cause the journey_segment resource to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"AdobeExperiencePlatform", "Custom"}, false),
},
},
}
)
func getAllJourneySegments(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
journeyApi := platformclientv2.NewJourneyApiWithConfig(clientConfig)
pageCount := 1 // Needed because of broken journey common paging
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
journeySegments, resp, getErr := journeyApi.GetJourneySegments("", pageSize, pageNum, true, nil, nil, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("Failed to get page of journey segments error: %s", getErr), resp)
}
if journeySegments.Entities == nil || len(*journeySegments.Entities) == 0 {
break
}
for _, journeySegment := range *journeySegments.Entities {
resources[*journeySegment.Id] = &resourceExporter.ResourceMeta{Name: *journeySegment.DisplayName}
}
pageCount = *journeySegments.PageCount
}
return resources, nil
}
func JourneySegmentExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllJourneySegments),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceJourneySegment() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Journey Segment",
CreateContext: provider.CreateWithPooledClient(createJourneySegment),
ReadContext: provider.ReadWithPooledClient(readJourneySegment),
UpdateContext: provider.UpdateWithPooledClient(updateJourneySegment),
DeleteContext: provider.DeleteWithPooledClient(deleteJourneySegment),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: journeySegmentSchema,
}
}
func createJourneySegment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
journeySegment := buildSdkJourneySegment(d)
log.Printf("Creating journey segment %s", *journeySegment.DisplayName)
result, resp, err := journeyApi.PostJourneySegments(*journeySegment)
if err != nil {
input, _ := util.InterfaceToJson(*journeySegment)
return util.BuildAPIDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("failed to create journey segment %s: %s\n(input: %+v)", *journeySegment.DisplayName, err, input), resp)
}
d.SetId(*result.Id)
log.Printf("Created journey segment %s %s", *result.DisplayName, *result.Id)
return readJourneySegment(ctx, d, meta)
}
func readJourneySegment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceJourneySegment(), constants.DefaultConsistencyChecks, "genesyscloud_journey_segment")
log.Printf("Reading journey segment %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
journeySegment, resp, getErr := journeyApi.GetJourneySegment(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("failed to read journey segment %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("failed to read journey segment %s | error: %s", d.Id(), getErr), resp))
}
flattenJourneySegment(d, journeySegment)
log.Printf("Read journey segment %s %s", d.Id(), *journeySegment.DisplayName)
return cc.CheckState(d)
})
}
func updateJourneySegment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
patchSegment := buildSdkPatchSegment(d)
log.Printf("Updating journey segment %s", d.Id())
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current journey segment version
journeySegment, resp, getErr := journeyApi.GetJourneySegment(d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("Failed to read current journey segment %s error: %s", d.Id(), getErr), resp)
}
patchSegment.Version = journeySegment.Version
_, resp, patchErr := journeyApi.PatchJourneySegment(d.Id(), *patchSegment)
if patchErr != nil {
input, _ := util.InterfaceToJson(*patchSegment)
return resp, util.BuildAPIDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("Failed to update journey segment %s (input: %+v) error: %s", *patchSegment.DisplayName, input, patchErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated journey segment %s", d.Id())
return readJourneySegment(ctx, d, meta)
}
func deleteJourneySegment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
displayName := d.Get("display_name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
journeyApi := platformclientv2.NewJourneyApiWithConfig(sdkConfig)
log.Printf("Deleting journey segment with display name %s", displayName)
if resp, err := journeyApi.DeleteJourneySegment(d.Id()); err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("Failed to delete journey segment with display name %s: %s", displayName, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := journeyApi.GetJourneySegment(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// journey segment deleted
log.Printf("Deleted journey segment %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("error deleting journey segment %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_journey_segment", fmt.Sprintf("journey segment %s still exists", d.Id()), resp))
})
}
func flattenJourneySegment(d *schema.ResourceData, journeySegment *platformclientv2.Journeysegment) {
d.Set("is_active", *journeySegment.IsActive)
d.Set("display_name", *journeySegment.DisplayName)
resourcedata.SetNillableValue(d, "description", journeySegment.Description)
d.Set("color", *journeySegment.Color)
d.Set("scope", *journeySegment.Scope)
resourcedata.SetNillableValue(d, "should_display_to_agent", journeySegment.ShouldDisplayToAgent)
resourcedata.SetNillableValue(d, "context", lists.FlattenAsList(journeySegment.Context, flattenContext))
resourcedata.SetNillableValue(d, "journey", lists.FlattenAsList(journeySegment.Journey, flattenJourney))
resourcedata.SetNillableValue(d, "external_segment", lists.FlattenAsList(journeySegment.ExternalSegment, flattenExternalSegment))
resourcedata.SetNillableValue(d, "assignment_expiration_days", journeySegment.AssignmentExpirationDays)
}
func buildSdkJourneySegment(journeySegment *schema.ResourceData) *platformclientv2.Journeysegmentrequest {
isActive := journeySegment.Get("is_active").(bool)
displayName := journeySegment.Get("display_name").(string)
description := resourcedata.GetNillableValue[string](journeySegment, "description")
color := journeySegment.Get("color").(string)
scope := journeySegment.Get("scope").(string)
shouldDisplayToAgent := resourcedata.GetNillableBool(journeySegment, "should_display_to_agent")
sdkContext := resourcedata.BuildSdkListFirstElement(journeySegment, "context", buildSdkRequestContext, false)
journey := resourcedata.BuildSdkListFirstElement(journeySegment, "journey", buildSdkRequestJourney, false)
externalSegment := resourcedata.BuildSdkListFirstElement(journeySegment, "external_segment", buildSdkExternalSegment, true)
assignmentExpirationDays := resourcedata.GetNillableValue[int](journeySegment, "assignment_expiration_days")
return &platformclientv2.Journeysegmentrequest{
IsActive: &isActive,
DisplayName: &displayName,
Description: description,
Color: &color,
Scope: &scope,
ShouldDisplayToAgent: shouldDisplayToAgent,
Context: sdkContext,
Journey: journey,
ExternalSegment: externalSegment,
AssignmentExpirationDays: assignmentExpirationDays,
}
}
func buildSdkPatchSegment(journeySegment *schema.ResourceData) *platformclientv2.Patchsegment {
isActive := journeySegment.Get("is_active").(bool)
displayName := journeySegment.Get("display_name").(string)
description := resourcedata.GetNillableValue[string](journeySegment, "description")
color := journeySegment.Get("color").(string)
shouldDisplayToAgent := resourcedata.GetNillableBool(journeySegment, "should_display_to_agent")
sdkContext := resourcedata.BuildSdkListFirstElement(journeySegment, "context", buildSdkPatchContext, false)
journey := resourcedata.BuildSdkListFirstElement(journeySegment, "journey", buildSdkPatchJourney, false)
externalSegment := resourcedata.BuildSdkListFirstElement(journeySegment, "external_segment", buildSdkPatchExternalSegment, true)
assignmentExpirationDays := resourcedata.GetNillableValue[int](journeySegment, "assignment_expiration_days")
sdkPatchSegment := platformclientv2.Patchsegment{}
sdkPatchSegment.SetField("IsActive", &isActive)
sdkPatchSegment.SetField("DisplayName", &displayName)
sdkPatchSegment.SetField("Description", description)
sdkPatchSegment.SetField("Color", &color)
sdkPatchSegment.SetField("ShouldDisplayToAgent", shouldDisplayToAgent)
sdkPatchSegment.SetField("Context", sdkContext)
sdkPatchSegment.SetField("Journey", journey)
sdkPatchSegment.SetField("ExternalSegment", externalSegment)
if assignmentExpirationDays != nil {
sdkPatchSegment.SetField("AssignmentExpirationDays", assignmentExpirationDays)
}
return &sdkPatchSegment
}
func flattenContext(context *platformclientv2.Context) map[string]interface{} {
if len(*context.Patterns) == 0 {
return nil
}
contextMap := make(map[string]interface{})
contextMap["patterns"] = *lists.FlattenList(context.Patterns, flattenContextPattern)
return contextMap
}
func buildSdkRequestContext(context map[string]interface{}) *platformclientv2.Requestcontext {
patterns := &[]platformclientv2.Requestcontextpattern{}
if context != nil {
patterns = stringmap.BuildSdkList(context, "patterns", buildSdkRequestContextPattern)
}
return &platformclientv2.Requestcontext{
Patterns: patterns,
}
}
func buildSdkPatchContext(context map[string]interface{}) *platformclientv2.Patchcontext {
patterns := &[]platformclientv2.Patchcontextpattern{}
if context != nil {
patterns = stringmap.BuildSdkList(context, "patterns", buildSdkPatchContextPattern)
}
return &platformclientv2.Patchcontext{
Patterns: patterns,
}
}
func flattenContextPattern(contextPattern *platformclientv2.Contextpattern) map[string]interface{} {
contextPatternMap := make(map[string]interface{})
contextPatternMap["criteria"] = *lists.FlattenList(contextPattern.Criteria, flattenEntityTypeCriteria)
return contextPatternMap
}
func buildSdkRequestContextPattern(contextPattern map[string]interface{}) *platformclientv2.Requestcontextpattern {
return &platformclientv2.Requestcontextpattern{
Criteria: stringmap.BuildSdkList(contextPattern, "criteria", buildSdkRequestEntityTypeCriteria),
}
}
func buildSdkPatchContextPattern(contextPattern map[string]interface{}) *platformclientv2.Patchcontextpattern {
return &platformclientv2.Patchcontextpattern{
Criteria: stringmap.BuildSdkList(contextPattern, "criteria", buildSdkPatchEntityTypeCriteria),
}
}
func flattenEntityTypeCriteria(entityTypeCriteria *platformclientv2.Entitytypecriteria) map[string]interface{} {
entityTypeCriteriaMap := make(map[string]interface{})
entityTypeCriteriaMap["key"] = *entityTypeCriteria.Key
entityTypeCriteriaMap["values"] = lists.StringListToSet(*entityTypeCriteria.Values)
entityTypeCriteriaMap["should_ignore_case"] = *entityTypeCriteria.ShouldIgnoreCase
entityTypeCriteriaMap["operator"] = *entityTypeCriteria.Operator
entityTypeCriteriaMap["entity_type"] = *entityTypeCriteria.EntityType
return entityTypeCriteriaMap
}
func buildSdkRequestEntityTypeCriteria(entityTypeCriteria map[string]interface{}) *platformclientv2.Requestentitytypecriteria {
key := entityTypeCriteria["key"].(string)
values := stringmap.BuildSdkStringList(entityTypeCriteria, "values")
shouldIgnoreCase := entityTypeCriteria["should_ignore_case"].(bool)
operator := entityTypeCriteria["operator"].(string)
entityType := entityTypeCriteria["entity_type"].(string)
return &platformclientv2.Requestentitytypecriteria{
Key: &key,
Values: values,
ShouldIgnoreCase: &shouldIgnoreCase,
Operator: &operator,
EntityType: &entityType,
}
}
func buildSdkPatchEntityTypeCriteria(entityTypeCriteria map[string]interface{}) *platformclientv2.Patchentitytypecriteria {
key := entityTypeCriteria["key"].(string)
values := stringmap.BuildSdkStringList(entityTypeCriteria, "values")
shouldIgnoreCase := entityTypeCriteria["should_ignore_case"].(bool)
operator := entityTypeCriteria["operator"].(string)
entityType := entityTypeCriteria["entity_type"].(string)
return &platformclientv2.Patchentitytypecriteria{
Key: &key,
Values: values,
ShouldIgnoreCase: &shouldIgnoreCase,
Operator: &operator,
EntityType: &entityType,
}
}
func flattenJourney(journey *platformclientv2.Journey) map[string]interface{} {
if len(*journey.Patterns) == 0 {
return nil
}
journeyMap := make(map[string]interface{})
journeyMap["patterns"] = *lists.FlattenList(journey.Patterns, flattenJourneyPattern)
return journeyMap
}
func buildSdkRequestJourney(journey map[string]interface{}) *platformclientv2.Requestjourney {
patterns := &[]platformclientv2.Requestjourneypattern{}
if journey != nil {
patterns = stringmap.BuildSdkList(journey, "patterns", buildSdkRequestJourneyPattern)
}
return &platformclientv2.Requestjourney{
Patterns: patterns,
}
}
func buildSdkPatchJourney(journey map[string]interface{}) *platformclientv2.Patchjourney {
patterns := &[]platformclientv2.Patchjourneypattern{}
if journey != nil {
patterns = stringmap.BuildSdkList(journey, "patterns", buildSdkPatchJourneyPattern)
}
return &platformclientv2.Patchjourney{
Patterns: patterns,
}
}
func flattenJourneyPattern(journeyPattern *platformclientv2.Journeypattern) map[string]interface{} {
journeyPatternMap := make(map[string]interface{})
journeyPatternMap["criteria"] = *lists.FlattenList(journeyPattern.Criteria, flattenCriteria)
journeyPatternMap["count"] = *journeyPattern.Count
journeyPatternMap["stream_type"] = *journeyPattern.StreamType
journeyPatternMap["session_type"] = *journeyPattern.SessionType
stringmap.SetValueIfNotNil(journeyPatternMap, "event_name", journeyPattern.EventName)
return journeyPatternMap
}
func buildSdkRequestJourneyPattern(journeyPattern map[string]interface{}) *platformclientv2.Requestjourneypattern {
criteria := stringmap.BuildSdkList(journeyPattern, "criteria", buildSdkRequestCriteria)
count := journeyPattern["count"].(int)
streamType := journeyPattern["stream_type"].(string)
sessionType := journeyPattern["session_type"].(string)
eventName := stringmap.GetNonDefaultValue[string](journeyPattern, "event_name")
return &platformclientv2.Requestjourneypattern{
Criteria: criteria,
Count: &count,
StreamType: &streamType,
SessionType: &sessionType,
EventName: eventName,
}
}
func buildSdkPatchJourneyPattern(journeyPattern map[string]interface{}) *platformclientv2.Patchjourneypattern {
criteria := stringmap.BuildSdkList(journeyPattern, "criteria", buildSdkPatchCriteria)
count := journeyPattern["count"].(int)
streamType := journeyPattern["stream_type"].(string)
sessionType := journeyPattern["session_type"].(string)
eventName := stringmap.GetNonDefaultValue[string](journeyPattern, "event_name")
return &platformclientv2.Patchjourneypattern{
Criteria: criteria,
Count: &count,
StreamType: &streamType,
SessionType: &sessionType,
EventName: eventName,
}
}
func flattenCriteria(criteria *platformclientv2.Criteria) map[string]interface{} {
criteriaMap := make(map[string]interface{})
criteriaMap["key"] = *criteria.Key
criteriaMap["values"] = lists.StringListToSet(*criteria.Values)
criteriaMap["should_ignore_case"] = *criteria.ShouldIgnoreCase
criteriaMap["operator"] = *criteria.Operator
return criteriaMap
}
func buildSdkRequestCriteria(criteria map[string]interface{}) *platformclientv2.Requestcriteria {
key := criteria["key"].(string)
values := stringmap.BuildSdkStringList(criteria, "values")
shouldIgnoreCase := criteria["should_ignore_case"].(bool)
operator := criteria["operator"].(string)
return &platformclientv2.Requestcriteria{
Key: &key,
Values: values,
ShouldIgnoreCase: &shouldIgnoreCase,
Operator: &operator,
}
}
func buildSdkPatchCriteria(criteria map[string]interface{}) *platformclientv2.Patchcriteria {
key := criteria["key"].(string)
values := stringmap.BuildSdkStringList(criteria, "values")
shouldIgnoreCase := criteria["should_ignore_case"].(bool)
operator := criteria["operator"].(string)
return &platformclientv2.Patchcriteria{
Key: &key,
Values: values,
ShouldIgnoreCase: &shouldIgnoreCase,
Operator: &operator,
}
}
func flattenExternalSegment(externalSegment *platformclientv2.Externalsegment) map[string]interface{} {
externalSegmentMap := make(map[string]interface{})
externalSegmentMap["id"] = *externalSegment.Id
externalSegmentMap["name"] = *externalSegment.Name
externalSegmentMap["source"] = *externalSegment.Source
return externalSegmentMap
}
func buildSdkExternalSegment(externalSegment map[string]interface{}) *platformclientv2.Requestexternalsegment {
id := externalSegment["id"].(string)
name := externalSegment["name"].(string)
source := externalSegment["source"].(string)
return &platformclientv2.Requestexternalsegment{
Id: &id,
Name: &name,
Source: &source,
}
}
func buildSdkPatchExternalSegment(externalSegment map[string]interface{}) *platformclientv2.Patchexternalsegment {
name := externalSegment["name"].(string)
return &platformclientv2.Patchexternalsegment{
Name: &name,
}
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
knowledgeCategory = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Knowledge base name. Changing the name attribute will cause the knowledge_category resource to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": {
Description: "Knowledge base description",
Type: schema.TypeString,
Optional: true,
},
"parent_id": {
Description: "Knowledge category parent id",
Type: schema.TypeString,
Optional: true,
},
},
}
)
func getAllKnowledgeCategories(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
knowledgeBaseList := make([]platformclientv2.Knowledgebase, 0)
categoryEntities := make([]platformclientv2.Categoryresponse, 0)
resources := make(resourceExporter.ResourceIDMetaMap)
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(clientConfig)
// get published knowledge bases
publishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, true)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *publishedEntities...)
// get unpublished knowledge bases
unpublishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, false)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *unpublishedEntities...)
for _, knowledgeBase := range knowledgeBaseList {
partialEntities, err := getAllKnowledgeCategoryEntities(*knowledgeAPI, &knowledgeBase)
if err != nil {
return nil, err
}
categoryEntities = append(categoryEntities, *partialEntities...)
}
for _, knowledgeCategory := range categoryEntities {
id := fmt.Sprintf("%s,%s", *knowledgeCategory.Id, *knowledgeCategory.KnowledgeBase.Id)
resources[id] = &resourceExporter.ResourceMeta{Name: *knowledgeCategory.Name}
}
return resources, nil
}
func getAllKnowledgeCategoryEntities(knowledgeAPI platformclientv2.KnowledgeApi, knowledgeBase *platformclientv2.Knowledgebase) (*[]platformclientv2.Categoryresponse, diag.Diagnostics) {
var (
after string
entities []platformclientv2.Categoryresponse
)
const pageSize = 100
for i := 0; ; i++ {
knowledgeCategories, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseCategories(*knowledgeBase.Id, "", after, fmt.Sprintf("%v", pageSize), "", false, "", "", "", false)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to read knowledge document error: %s", getErr), resp)
}
if knowledgeCategories.Entities == nil || len(*knowledgeCategories.Entities) == 0 {
break
}
entities = append(entities, *knowledgeCategories.Entities...)
if knowledgeCategories.NextUri == nil || *knowledgeCategories.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*knowledgeCategories.NextUri, "after")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to parse after cursor from knowledge category nextUri"), err)
}
if after == "" {
break
}
}
return &entities, nil
}
func KnowledgeCategoryExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllKnowledgeCategories),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"knowledge_base_id": {RefType: "genesyscloud_knowledge_knowledgebase"},
},
}
}
func ResourceKnowledgeCategory() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Knowledge Category",
CreateContext: provider.CreateWithPooledClient(createKnowledgeCategory),
ReadContext: provider.ReadWithPooledClient(readKnowledgeCategory),
UpdateContext: provider.UpdateWithPooledClient(updateKnowledgeCategory),
DeleteContext: provider.DeleteWithPooledClient(deleteKnowledgeCategory),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"knowledge_base_id": {
Description: "Knowledge base id of the category",
Type: schema.TypeString,
Required: true,
},
"knowledge_category": {
Description: "Knowledge category id",
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: knowledgeCategory,
},
},
}
}
func createKnowledgeCategory(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
knowledgeBaseId := d.Get("knowledge_base_id").(string)
knowledgeCategory := d.Get("knowledge_category").([]interface{})[0].(map[string]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
knowledgeCategoryRequest := buildKnowledgeCategoryCreate(knowledgeCategory)
log.Printf("Creating knowledge category %s", knowledgeCategory["name"].(string))
knowledgeCategoryResponse, resp, err := knowledgeAPI.PostKnowledgeKnowledgebaseCategories(knowledgeBaseId, *knowledgeCategoryRequest)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to create knowledge category %s error: %s", d.Id(), err), resp)
}
id := fmt.Sprintf("%s,%s", *knowledgeCategoryResponse.Id, *knowledgeCategoryResponse.KnowledgeBase.Id)
d.SetId(id)
log.Printf("Created knowledge category %s", *knowledgeCategoryResponse.Id)
return readKnowledgeCategory(ctx, d, meta)
}
func readKnowledgeCategory(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeCategoryId := id[0]
knowledgeBaseId := id[1]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceKnowledgeCategory(), constants.DefaultConsistencyChecks, "genesyscloud_knowledge_category")
log.Printf("Reading knowledge category %s", knowledgeCategoryId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
knowledgeCategory, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseCategory(knowledgeBaseId, knowledgeCategoryId)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to read knowledge category %s | error: %s", knowledgeCategoryId, getErr), resp))
}
log.Printf("%s", getErr)
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to read knowledge category %s | error: %s", knowledgeCategoryId, getErr), resp))
}
newId := fmt.Sprintf("%s,%s", *knowledgeCategory.Id, *knowledgeCategory.KnowledgeBase.Id)
d.SetId(newId)
d.Set("knowledge_base_id", *knowledgeCategory.KnowledgeBase.Id)
d.Set("knowledge_category", flattenKnowledgeCategory(*knowledgeCategory))
log.Printf("Read knowledge category %s", knowledgeCategoryId)
return cc.CheckState(d)
})
}
func updateKnowledgeCategory(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeCategoryId := id[0]
knowledgeBaseId := id[1]
knowledgeCategory := d.Get("knowledge_category").([]interface{})[0].(map[string]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Updating knowledge category %s", knowledgeCategory["name"].(string))
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current knowledge category version
_, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseCategory(knowledgeBaseId, knowledgeCategoryId)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to read knowledge category %s error: %s", knowledgeCategoryId, getErr), resp)
}
knowledgeCategoryUpdate := buildKnowledgeCategoryUpdate(knowledgeCategory)
log.Printf("Updating knowledge category %s", knowledgeCategory["name"].(string))
_, resp, putErr := knowledgeAPI.PatchKnowledgeKnowledgebaseCategory(knowledgeBaseId, knowledgeCategoryId, *knowledgeCategoryUpdate)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to update knowledge category %s error: %s", knowledgeCategoryId, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated knowledge category %s %s", knowledgeCategory["name"].(string), knowledgeCategoryId)
return readKnowledgeCategory(ctx, d, meta)
}
func deleteKnowledgeCategory(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeCategoryId := id[0]
knowledgeBaseId := id[1]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Deleting knowledge category %s", id)
_, resp, err := knowledgeAPI.DeleteKnowledgeKnowledgebaseCategory(knowledgeBaseId, knowledgeCategoryId)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Failed to delete knowledge category %s error: %s", id, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := knowledgeAPI.GetKnowledgeKnowledgebaseCategory(knowledgeBaseId, knowledgeCategoryId)
if err != nil {
if util.IsStatus404(resp) {
// Knowledge category deleted
log.Printf("Deleted knowledge category %s", knowledgeCategoryId)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Error deleting knowledge category %s | error: %s", knowledgeCategoryId, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_category", fmt.Sprintf("Knowledge category %s still exists", knowledgeCategoryId), resp))
})
}
func buildKnowledgeCategoryUpdate(categoryIn map[string]interface{}) *platformclientv2.Categoryupdaterequest {
name := categoryIn["name"].(string)
categoryOut := platformclientv2.Categoryupdaterequest{
Name: &name,
}
if description, ok := categoryIn["description"].(string); ok && description != "" {
categoryOut.Description = &description
}
if parentId, ok := categoryIn["parent_id"].(string); ok && parentId != "" {
categoryOut.ParentCategoryId = &parentId
}
return &categoryOut
}
func buildKnowledgeCategoryCreate(categoryIn map[string]interface{}) *platformclientv2.Categorycreaterequest {
name := categoryIn["name"].(string)
categoryOut := platformclientv2.Categorycreaterequest{
Name: &name,
}
if description, ok := categoryIn["description"].(string); ok && description != "" {
categoryOut.Description = &description
}
if parentId, ok := categoryIn["parent_id"].(string); ok && parentId != "" {
categoryOut.ParentCategoryId = &parentId
}
return &categoryOut
}
func flattenKnowledgeCategory(categoryIn platformclientv2.Categoryresponse) []interface{} {
categoryOut := make(map[string]interface{})
if categoryIn.Name != nil {
categoryOut["name"] = *categoryIn.Name
}
if categoryIn.Description != nil {
categoryOut["description"] = *categoryIn.Description
}
if categoryIn.ParentCategory != nil && (*categoryIn.ParentCategory).Id != nil {
categoryOut["parent_id"] = (*categoryIn.ParentCategory).Id
}
return []interface{}{categoryOut}
}
package genesyscloud
import (
"context"
"encoding/json"
"fmt"
"log"
"net/url"
"strings"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
knowledgeDocument = &schema.Resource{
Schema: map[string]*schema.Schema{
"title": {
Description: "Document title",
Type: schema.TypeString,
Required: true,
},
"visible": {
Description: "Indicates if the knowledge document should be included in search results.",
Type: schema.TypeBool,
Required: true,
},
"alternatives": {
Description: "List of alternate phrases related to the title which improves search results.",
Type: schema.TypeList,
Optional: true,
Elem: documentAlternative,
},
"category_name": {
Description: "The name of the category associated with the document.",
Type: schema.TypeString,
Optional: true,
},
"label_names": {
Description: "The names of labels associated with the document.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
documentAlternative = &schema.Resource{
Schema: map[string]*schema.Schema{
"phrase": {
Description: "Alternate phrasing to the document title.",
Type: schema.TypeString,
Required: true,
},
"autocomplete": {
Description: "Autocomplete enabled for the alternate phrase.",
Type: schema.TypeBool,
Optional: true,
},
},
}
)
func getAllKnowledgeDocuments(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
knowledgeBaseList := make([]platformclientv2.Knowledgebase, 0)
documentEntities := make([]platformclientv2.Knowledgedocumentresponse, 0)
resources := make(resourceExporter.ResourceIDMetaMap)
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(clientConfig)
// get published knowledge bases
publishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, true)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *publishedEntities...)
// get unpublished knowledge bases
unpublishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, false)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *unpublishedEntities...)
for _, knowledgeBase := range knowledgeBaseList {
partialEntities, err := getAllKnowledgeDocumentEntities(*knowledgeAPI, &knowledgeBase, clientConfig)
if err != nil {
return nil, err
}
documentEntities = append(documentEntities, *partialEntities...)
}
for _, knowledgeDocument := range documentEntities {
id := fmt.Sprintf("%s,%s", *knowledgeDocument.Id, *knowledgeDocument.KnowledgeBase.Id)
resources[id] = &resourceExporter.ResourceMeta{Name: *knowledgeDocument.Title}
}
return resources, nil
}
func getAllKnowledgeDocumentEntities(knowledgeAPI platformclientv2.KnowledgeApi, knowledgeBase *platformclientv2.Knowledgebase, clientConfig *platformclientv2.Configuration) (*[]platformclientv2.Knowledgedocumentresponse, diag.Diagnostics) {
var (
after string
entities []platformclientv2.Knowledgedocumentresponse
)
resources := make(resourceExporter.ResourceIDMetaMap)
const pageSize = 100
// prepare base url
resourcePath := fmt.Sprintf("/api/v2/knowledge/knowledgebases/%s/documents", url.PathEscape(*knowledgeBase.Id))
listDocumentsBaseUrl := fmt.Sprintf("%s%s", knowledgeAPI.Configuration.BasePath, resourcePath)
for {
// prepare query params
queryParams := make(map[string]string, 0)
queryParams["after"] = after
queryParams["pageSize"] = fmt.Sprintf("%v", pageSize)
queryParams["includeDrafts"] = "true"
// prepare headers
headers := make(map[string]string)
headers["Authorization"] = fmt.Sprintf("Bearer %s", clientConfig.AccessToken)
headers["Content-Type"] = "application/json"
headers["Accept"] = "application/json"
// execute request
response, err := clientConfig.APIClient.CallAPI(listDocumentsBaseUrl, "GET", nil, headers, queryParams, nil, "", nil)
if err != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to read knowledge document list response error: %s", err), response)
}
// process response
var knowledgeDocuments platformclientv2.Knowledgedocumentresponselisting
unmarshalErr := json.Unmarshal(response.RawBody, &knowledgeDocuments)
if unmarshalErr != nil {
return nil, util.BuildDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to unmarshal knowledge document list response"), unmarshalErr)
}
/**
* Todo: restore direct SDK invocation and remove workaround once the SDK supports optional boolean args.
*/
// knowledgeDocuments, _, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocuments(*knowledgeBase.Id, "", after, fmt.Sprintf("%v", pageSize), "", nil, nil, true, true, nil, nil)
// if getErr != nil {
// return nil, diag.Errorf("Failed to get page of knowledge documents: %v", getErr)
// }
if knowledgeDocuments.Entities == nil || len(*knowledgeDocuments.Entities) == 0 {
break
}
entities = append(entities, *knowledgeDocuments.Entities...)
if knowledgeDocuments.NextUri == nil || *knowledgeDocuments.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*knowledgeDocuments.NextUri, "after")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to parse after cursor from knowledge document nextUri"), err)
}
if after == "" {
break
}
for _, knowledgeDocument := range *knowledgeDocuments.Entities {
id := fmt.Sprintf("%s,%s", *knowledgeDocument.Id, *knowledgeDocument.KnowledgeBase.Id)
resources[id] = &resourceExporter.ResourceMeta{Name: *knowledgeDocument.Title}
}
}
return &entities, nil
}
func KnowledgeDocumentExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllKnowledgeDocuments),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"knowledge_base_id": {RefType: "genesyscloud_knowledge_knowledgebase"},
},
}
}
func ResourceKnowledgeDocument() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Knowledge document",
CreateContext: provider.CreateWithPooledClient(createKnowledgeDocument),
ReadContext: provider.ReadWithPooledClient(readKnowledgeDocument),
UpdateContext: provider.UpdateWithPooledClient(updateKnowledgeDocument),
DeleteContext: provider.DeleteWithPooledClient(deleteKnowledgeDocument),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"knowledge_base_id": {
Description: "Knowledge base id",
Type: schema.TypeString,
Required: true,
},
"knowledge_document": {
Description: "Knowledge document request body",
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: knowledgeDocument,
},
"published": {
Description: "If true, the knowledge document will be published. If false, it will be a draft. The document can only be published if it has document variations.",
Type: schema.TypeBool,
Required: true,
},
},
}
}
func buildDocumentAlternatives(requestIn map[string]interface{}, knowledgeAPI *platformclientv2.KnowledgeApi, knowledgeBaseId string) *[]platformclientv2.Knowledgedocumentalternative {
if alternativesIn, ok := requestIn["alternatives"].([]interface{}); ok {
alternativesOut := make([]platformclientv2.Knowledgedocumentalternative, 0)
for _, alternative := range alternativesIn {
alternativeMap := alternative.(map[string]interface{})
phrase := alternativeMap["phrase"].(string)
autocomplete := alternativeMap["autocomplete"].(bool)
alternativeOut := platformclientv2.Knowledgedocumentalternative{
Phrase: &phrase,
Autocomplete: &autocomplete,
}
alternativesOut = append(alternativesOut, alternativeOut)
}
return &alternativesOut
}
return nil
}
func buildKnowledgeDocumentRequest(d *schema.ResourceData, knowledgeAPI *platformclientv2.KnowledgeApi, knowledgeBaseId string) (*platformclientv2.Knowledgedocumentreq, diag.Diagnostics) {
requestIn := d.Get("knowledge_document").([]interface{})[0].(map[string]interface{})
title := requestIn["title"].(string)
visible := requestIn["visible"].(bool)
requestOut := platformclientv2.Knowledgedocumentreq{
Title: &title,
Visible: &visible,
Alternatives: buildDocumentAlternatives(requestIn, knowledgeAPI, knowledgeBaseId),
}
if categoryName, ok := requestIn["category_name"].(string); ok && categoryName != "" {
pageSize := 1
knowledgeCategories, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseCategories(knowledgeBaseId, "", "", fmt.Sprintf("%v", pageSize), "", false, categoryName, "", "", false)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to get page of knowledge categories error: %s", getErr), resp)
}
if len(*knowledgeCategories.Entities) > 0 {
matchingCategory := (*knowledgeCategories.Entities)[0]
requestOut.CategoryId = matchingCategory.Id
}
}
if labelNames, ok := requestIn["label_names"].([]interface{}); ok && labelNames != nil {
labelStringList := lists.InterfaceListToStrings(labelNames)
pageSize := 1
labelIds := make([]string, 0)
for _, labelName := range labelStringList {
knowledgeLabels, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLabels(knowledgeBaseId, "", "", fmt.Sprintf("%v", pageSize), labelName, false)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to get page of knowledge labels error: %s", getErr), resp)
}
if len(*knowledgeLabels.Entities) > 0 {
matchingLabel := (*knowledgeLabels.Entities)[0]
labelIds = append(labelIds, *matchingLabel.Id)
}
}
requestOut.LabelIds = &labelIds
}
return &requestOut, nil
}
func flattenDocumentAlternatives(alternativesIn *[]platformclientv2.Knowledgedocumentalternative) []interface{} {
if alternativesIn == nil || len(*alternativesIn) == 0 {
return nil
}
alternativesOut := make([]interface{}, 0)
for _, alternativeIn := range *alternativesIn {
alternativeOut := make(map[string]interface{})
if alternativeIn.Phrase != nil {
alternativeOut["phrase"] = *alternativeIn.Phrase
}
if alternativeIn.Autocomplete != nil {
alternativeOut["autocomplete"] = *alternativeIn.Autocomplete
}
alternativesOut = append(alternativesOut, alternativeOut)
}
return alternativesOut
}
func flattenKnowledgeDocument(documentIn *platformclientv2.Knowledgedocumentresponse, knowledgeAPI *platformclientv2.KnowledgeApi, knowledgeBaseId string) ([]interface{}, error) {
if documentIn == nil {
return nil, nil
}
documentOut := make(map[string]interface{})
documentOut["alternatives"] = flattenDocumentAlternatives(documentIn.Alternatives)
if documentIn.Title != nil {
documentOut["title"] = *documentIn.Title
}
if documentIn.Visible != nil {
documentOut["visible"] = *documentIn.Visible
}
if documentIn.Category != nil {
// use the id to retrieve the category name
knowledgeCategory, _, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseCategory(knowledgeBaseId, *documentIn.Category.Id)
if getErr != nil {
return nil, fmt.Errorf("Failed to get knowledge category: %v", getErr)
}
if knowledgeCategory.Name != nil {
documentOut["category_name"] = knowledgeCategory.Name
}
}
if documentIn.Labels != nil && len(*documentIn.Labels) > 0 {
labelNames := make([]string, 0)
for _, label := range *documentIn.Labels {
knowledgeLabel, _, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLabel(knowledgeBaseId, *label.Id)
if getErr != nil {
return nil, fmt.Errorf("Failed to get knowledge label: %v", getErr)
}
if knowledgeLabel.Name != nil {
labelNames = append(labelNames, *knowledgeLabel.Name)
}
}
documentOut["label_names"] = labelNames
}
return []interface{}{documentOut}, nil
}
func createKnowledgeDocument(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
knowledgeBaseId := d.Get("knowledge_base_id").(string)
published := d.Get("published").(bool)
body, buildErr := buildKnowledgeDocumentRequest(d, knowledgeAPI, knowledgeBaseId)
if buildErr != nil {
return buildErr
}
log.Printf("Creating knowledge document")
knowledgeDocument, resp, err := knowledgeAPI.PostKnowledgeKnowledgebaseDocuments(knowledgeBaseId, *body)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to create knowledge document %s error: %s", d.Id(), err), resp)
}
if published {
_, resp, versionErr := knowledgeAPI.PostKnowledgeKnowledgebaseDocumentVersions(knowledgeBaseId, *knowledgeDocument.Id, platformclientv2.Knowledgedocumentversion{})
if versionErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to publish knowledge document error: %s", err), resp)
}
}
id := fmt.Sprintf("%s,%s", *knowledgeDocument.Id, knowledgeBaseId)
d.SetId(id)
log.Printf("Created knowledge document %s", *knowledgeDocument.Id)
return readKnowledgeDocument(ctx, d, meta)
}
func readKnowledgeDocument(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeDocumentId := id[0]
knowledgeBaseId := id[1]
state := "Draft"
if d.Get("published").(bool) == true {
state = "Published"
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceKnowledgeDocument(), constants.DefaultConsistencyChecks, "genesyscloud_knowledge_document")
log.Printf("Reading knowledge document %s", knowledgeDocumentId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
knowledgeDocument, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocument(knowledgeBaseId, knowledgeDocumentId, nil, state)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to read knowledge document %s: %s", knowledgeDocumentId, getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to read knowledge document %s: %s", knowledgeDocumentId, getErr), resp))
}
// required
id := fmt.Sprintf("%s,%s", *knowledgeDocument.Id, knowledgeBaseId)
d.SetId(id)
d.Set("knowledge_base_id", *knowledgeDocument.KnowledgeBase.Id)
flattenedDocument, err := flattenKnowledgeDocument(knowledgeDocument, knowledgeAPI, knowledgeBaseId)
if err != nil {
return retry.NonRetryableError(err)
}
d.Set("knowledge_document", flattenedDocument)
if *knowledgeDocument.State == "Published" {
d.Set("published", true)
} else {
d.Set("published", false)
}
log.Printf("Read Knowledge document %s", *knowledgeDocument.Id)
checkState := cc.CheckState(d)
return checkState
})
}
func updateKnowledgeDocument(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeDocumentId := id[0]
knowledgeBaseId := d.Get("knowledge_base_id").(string)
state := "Draft"
if d.Get("published").(bool) == true {
state = "Published"
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Updating Knowledge document %s", knowledgeDocumentId)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current Knowledge document version
_, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocument(knowledgeBaseId, knowledgeDocumentId, nil, state)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to read knowledge document %s error: %s", knowledgeDocumentId, getErr), resp)
}
update, err := buildKnowledgeDocumentRequest(d, knowledgeAPI, knowledgeBaseId)
if err != nil {
return nil, err
}
log.Printf("Updating knowledge document %s", knowledgeDocumentId)
_, resp, putErr := knowledgeAPI.PatchKnowledgeKnowledgebaseDocument(knowledgeBaseId, knowledgeDocumentId, *update)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to update knowledge document %s error: %s", knowledgeDocumentId, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Knowledge document %s", knowledgeDocumentId)
return readKnowledgeDocument(ctx, d, meta)
}
func deleteKnowledgeDocument(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeDocumentId := id[0]
knowledgeBaseId := id[1]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Deleting Knowledge document %s", knowledgeDocumentId)
resp, err := knowledgeAPI.DeleteKnowledgeKnowledgebaseDocument(knowledgeBaseId, knowledgeDocumentId)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Failed to delete knowledge document %s error: %s", knowledgeDocumentId, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
state := "Draft"
if d.Get("published").(bool) == true {
state = "Published"
}
_, resp, err := knowledgeAPI.GetKnowledgeKnowledgebaseDocument(knowledgeDocumentId, knowledgeBaseId, nil, state)
if err != nil {
if util.IsStatus404(resp) {
// Knowledge document deleted
log.Printf("Deleted Knowledge document %s", knowledgeDocumentId)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Error deleting Knowledge document %s | error: %s", knowledgeDocumentId, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document", fmt.Sprintf("Knowledge document %s still exists", knowledgeDocumentId), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
knowledgeDocumentVariation = &schema.Resource{
Schema: map[string]*schema.Schema{
"body": {
Description: "The content for the variation.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: documentBody,
},
"document_version": {
Description: "The version of the document.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: addressableEntityRef,
Computed: true,
},
},
}
documentBody = &schema.Resource{
Schema: map[string]*schema.Schema{
"blocks": {
Description: "The content for the variation.",
Type: schema.TypeList,
Required: true,
Elem: documentBodyBlock,
},
},
}
documentBodyBlock = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "The type of the block for the body. This determines which body block object (paragraph, list, video or image) would have a value.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Paragraph", "Image", "Video", "OrderedList", "UnorderedList"}, false),
},
"paragraph": {
Description: "Paragraph. It must contain a value if the type of the block is Paragraph.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: documentBodyParagraph,
},
"image": {
Description: "Image. It must contain a value if the type of the block is Image.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: documentBodyImage,
},
"video": {
Description: "Video. It must contain a value if the type of the block is Video.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: documentBodyVideo,
},
"list": {
Description: "List. It must contain a value if the type of the block is UnorderedList or OrderedList.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: documentBodyList,
},
},
}
documentBodyParagraph = &schema.Resource{
Schema: map[string]*schema.Schema{
"blocks": {
Description: "The content for the variation.",
Type: schema.TypeList,
Optional: true,
Elem: documentContentBlock,
},
},
}
addressableEntityRef = &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Description: "Id",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
documentBodyImage = &schema.Resource{
Schema: map[string]*schema.Schema{
"url": {
Description: "The URL for the image.",
Type: schema.TypeString,
Required: true,
},
"hyperlink": {
Description: "The URL of the page that the hyperlink goes to.",
Type: schema.TypeString,
Optional: true,
},
},
}
documentBodyVideo = &schema.Resource{
Schema: map[string]*schema.Schema{
"url": {
Description: "The URL for the video.",
Type: schema.TypeString,
Required: true,
},
},
}
documentBodyList = &schema.Resource{
Schema: map[string]*schema.Schema{
"blocks": {
Description: "The list of items for an OrderedList or an UnorderedList.",
Type: schema.TypeList,
Optional: true,
Elem: documentBodyListBlock,
},
},
}
documentBodyListBlock = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "The type of the list block.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"ListItem"}, false),
},
"blocks": {
Description: "The list of items for an OrderedList or an UnorderedList.",
Type: schema.TypeList,
Required: true,
Elem: documentContentBlock,
},
},
}
documentContentBlock = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "The type of the content block.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Text", "Image"}, false),
},
"text": {
Description: "Text. It must contain a value if the type of the block is Text.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: documentText,
},
"image": {
Description: "Image. It must contain a value if the type of the block is Image.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: documentBodyImage,
},
},
}
documentText = &schema.Resource{
Schema: map[string]*schema.Schema{
"text": {
Description: "Text.",
Type: schema.TypeString,
Required: true,
},
"marks": {
Description: "The unique list of marks (whether it is bold and/or underlined etc.) for the text. Valid values: Bold | Italic | Underline",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"hyperlink": {
Description: "The URL of the page that the hyperlink goes to.",
Type: schema.TypeString,
Optional: true,
},
},
}
)
func getAllKnowledgeDocumentVariations(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
knowledgeBaseList := make([]platformclientv2.Knowledgebase, 0)
resources := make(resourceExporter.ResourceIDMetaMap)
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(clientConfig)
// get published knowledge bases
publishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, true)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *publishedEntities...)
// get unpublished knowledge bases
unpublishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, false)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *unpublishedEntities...)
for _, knowledgeBase := range knowledgeBaseList {
variationEntities, err := getAllKnowledgeDocumentEntities(*knowledgeAPI, &knowledgeBase, clientConfig)
if err != nil {
return nil, err
}
// retrieve the documents for each knowledge base
for _, knowledgeDocument := range *variationEntities {
const pageSize = 100
// parse document state
var documentState string
isValidState := strings.EqualFold(*knowledgeDocument.State, "Published") || strings.EqualFold(*knowledgeDocument.State, "Draft")
if isValidState {
documentState = *knowledgeDocument.State
}
// get the variations for each document
knowledgeDocumentVariations, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariations(*knowledgeBase.Id, *knowledgeDocument.Id, "", "", fmt.Sprintf("%v", pageSize), documentState)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to get page of knowledge document variations error: %v", err), resp)
}
if knowledgeDocumentVariations.Entities == nil || len(*knowledgeDocumentVariations.Entities) == 0 {
break
}
for _, knowledgeDocumentVariation := range *knowledgeDocumentVariations.Entities {
id := fmt.Sprintf("%s %s %s", *knowledgeDocumentVariation.Id, *knowledgeDocument.KnowledgeBase.Id, *knowledgeDocument.Id)
resources[id] = &resourceExporter.ResourceMeta{Name: "variation " + uuid.NewString()}
}
}
}
return resources, nil
}
func KnowledgeDocumentVariationExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllKnowledgeDocumentVariations),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"knowledge_base_id": {RefType: "genesyscloud_knowledge_knowledgebase"},
"knowledge_document_id": {RefType: "genesyscloud_knowledge_document"},
},
}
}
func ResourceKnowledgeDocumentVariation() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Knowledge Document Variation",
CreateContext: provider.CreateWithPooledClient(createKnowledgeDocumentVariation),
ReadContext: provider.ReadWithPooledClient(readKnowledgeDocumentVariation),
UpdateContext: provider.UpdateWithPooledClient(updateKnowledgeDocumentVariation),
DeleteContext: provider.DeleteWithPooledClient(deleteKnowledgeDocumentVariation),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"knowledge_base_id": {
Description: "Knowledge base id of the label",
Type: schema.TypeString,
Required: true,
},
"knowledge_document_id": {
Description: "Knowledge base id of the label",
Type: schema.TypeString,
Required: true,
},
"published": {
Description: "If true, the document will be published with the new variation. If false, the updated document will be in a draft state.",
Type: schema.TypeBool,
Optional: true,
},
"knowledge_document_variation": {
Description: "Knowledge document variation",
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: knowledgeDocumentVariation,
},
},
}
}
func createKnowledgeDocumentVariation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
knowledgeBaseId := d.Get("knowledge_base_id").(string)
documentResourceId := d.Get("knowledge_document_id").(string)
knowledgeDocumentId := strings.Split(documentResourceId, ",")[0]
knowledgeDocumentVariation := d.Get("knowledge_document_variation").([]interface{})[0].(map[string]interface{})
published := false
if publishedIn, ok := d.GetOk("published"); ok {
published = publishedIn.(bool)
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
knowledgeDocumentVariationRequest := buildKnowledgeDocumentVariation(knowledgeDocumentVariation)
log.Printf("Creating knowledge document variation for document %s", knowledgeDocumentId)
knowledgeDocumentVariationResponse, resp, err := knowledgeAPI.PostKnowledgeKnowledgebaseDocumentVariations(knowledgeBaseId, knowledgeDocumentId, *knowledgeDocumentVariationRequest)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to create variation for knowledge document %s error: %s", d.Id(), err), resp)
}
if published == true {
_, resp, versionErr := knowledgeAPI.PostKnowledgeKnowledgebaseDocumentVersions(knowledgeBaseId, knowledgeDocumentId, platformclientv2.Knowledgedocumentversion{})
if versionErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to publish knowledge document error: %s", err), resp)
}
}
id := fmt.Sprintf("%s %s %s", *knowledgeDocumentVariationResponse.Id, knowledgeBaseId, documentResourceId)
d.SetId(id)
log.Printf("Created knowledge document variation %s", *knowledgeDocumentVariationResponse.Id)
return readKnowledgeDocumentVariation(ctx, d, meta)
}
func readKnowledgeDocumentVariation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
documentVariationId := id[0]
knowledgeBaseId := id[1]
documentResourceId := id[2]
knowledgeDocumentId := strings.Split(documentResourceId, ",")[0]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceKnowledgeDocumentVariation(), constants.DefaultConsistencyChecks, "genesyscloud_knowledge_document_variation")
documentState := ""
// If the published flag is set, use it to set documentState param
if published, ok := d.GetOk("published"); ok {
if published == true {
documentState = "Published"
} else {
documentState = "Draft"
}
}
log.Printf("Reading knowledge document variation %s", documentVariationId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
var knowledgeDocumentVariation *platformclientv2.Documentvariation
/*
* If the published flag is not set, get both published and draft variation and choose the most recent
* If it is set, base the document state param off the flag.
* The published flag has to be optional for the import case, where only the resource ID is available.
* If the published flag were required, it would cause consistency issues for the import state.
*/
if documentState == "" {
publishedVariation, resp, publishedErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId, "Published")
if publishedErr != nil {
// Published version may or may not exist, so if status is 404, sleep and retry once and then move on to retrieve draft variation.
if util.IsStatus404(resp) {
time.Sleep(2 * time.Second)
retryVariation, retryResp, retryErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId, "Published")
if retryErr != nil {
if !util.IsStatus404(retryResp) {
log.Printf("%s", retryErr)
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to read knowledge document variation %s: %s", documentVariationId, retryErr), retryResp))
}
} else {
publishedVariation = retryVariation
}
} else {
log.Printf("%s", publishedErr)
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to read knowledge document variation %s: %s", documentVariationId, publishedErr), resp))
}
}
draftVariation, resp, draftErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId, "Draft")
if draftErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to read knowledge document variation %s: %s", documentVariationId, draftErr), resp))
}
log.Printf("%s", draftErr)
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to read knowledge document variation %s: %s", documentVariationId, draftErr), resp))
}
if publishedVariation != nil && publishedVariation.DateModified != nil && publishedVariation.DateModified.After(*draftVariation.DateModified) {
knowledgeDocumentVariation = publishedVariation
} else {
knowledgeDocumentVariation = draftVariation
}
} else {
variation, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId, documentState)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to read knowledge document variation %s | error: %s", documentVariationId, getErr), resp))
}
log.Printf("%s", getErr)
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to read knowledge document variation %s | error: %s", documentVariationId, getErr), resp))
}
knowledgeDocumentVariation = variation
}
newId := fmt.Sprintf("%s %s %s", *knowledgeDocumentVariation.Id, *knowledgeDocumentVariation.Document.KnowledgeBase.Id, documentResourceId)
d.SetId(newId)
d.Set("knowledge_base_id", *knowledgeDocumentVariation.Document.KnowledgeBase.Id)
d.Set("knowledge_document_id", documentResourceId)
d.Set("knowledge_document_variation", flattenKnowledgeDocumentVariation(*knowledgeDocumentVariation))
if knowledgeDocumentVariation.DocumentVersion != nil && knowledgeDocumentVariation.DocumentVersion.Id != nil && len(*knowledgeDocumentVariation.DocumentVersion.Id) > 0 {
d.Set("published", true)
} else {
d.Set("published", false)
}
log.Printf("Read knowledge document variation %s", documentVariationId)
return cc.CheckState(d)
})
}
func updateKnowledgeDocumentVariation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
documentVariationId := id[0]
knowledgeBaseId := id[1]
documentResourceId := id[2]
knowledgeDocumentId := strings.Split(documentResourceId, ",")[0]
knowledgeDocumentVariation := d.Get("knowledge_document_variation").([]interface{})[0].(map[string]interface{})
published := false
if publishedIn, ok := d.GetOk("published"); ok {
published = publishedIn.(bool)
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Updating knowledge document variation %s", documentVariationId)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current knowledge document variation version
_, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId, "Draft")
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to read knowledge document variation %s error: %s", id, getErr), resp)
}
knowledgeDocumentVariationUpdate := buildKnowledgeDocumentVariationUpdate(knowledgeDocumentVariation)
log.Printf("Updating knowledge document variation %s", documentVariationId)
_, resp, putErr := knowledgeAPI.PatchKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId, *knowledgeDocumentVariationUpdate)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to update knowledge document variation %s error: %s", documentVariationId, putErr), resp)
}
if published == true {
_, resp, versionErr := knowledgeAPI.PostKnowledgeKnowledgebaseDocumentVersions(knowledgeBaseId, knowledgeDocumentId, platformclientv2.Knowledgedocumentversion{})
if versionErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to publish knowledge document %s error: %s", id, versionErr), resp)
}
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated knowledge document variation %s", documentVariationId)
return readKnowledgeDocumentVariation(ctx, d, meta)
}
func deleteKnowledgeDocumentVariation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
documentVariationId := id[0]
knowledgeBaseId := id[1]
documentResourceId := id[2]
knowledgeDocumentId := strings.Split(documentResourceId, ",")[0]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
published := false
if publishedIn, ok := d.GetOk("published"); ok {
published = publishedIn.(bool)
}
log.Printf("Deleting knowledge document variation %s", id)
resp, err := knowledgeAPI.DeleteKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to delete knowledge document variation %s error: %s", id, err), resp)
}
if published == true {
/*
* If the published flag is set, attempt to publish a new document version without the variation.
* However a document cannot be published if it has no variations, so first check that the document has other variations
* A new document version can only be published if there are other variations than the one being removed
*/
pageSize := 3
variations, resp, variationErr := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariations(knowledgeBaseId, knowledgeDocumentId, "", "", fmt.Sprintf("%v", pageSize), "Draft")
if variationErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to retrieve knowledge document variations error: %s", err), resp)
}
if len(*variations.Entities) > 0 {
_, resp, versionErr := knowledgeAPI.PostKnowledgeKnowledgebaseDocumentVersions(knowledgeBaseId, knowledgeDocumentId, platformclientv2.Knowledgedocumentversion{})
if versionErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Failed to publish knowledge document error: %s", err), resp)
}
}
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
// The DELETE resource for knowledge document variations only removes draft variations. So set the documentState param to "Draft" for the check
_, resp, err := knowledgeAPI.GetKnowledgeKnowledgebaseDocumentVariation(documentVariationId, knowledgeDocumentId, knowledgeBaseId, "Draft")
if err != nil {
if util.IsStatus404(resp) {
// Knowledge base deleted
log.Printf("Deleted knowledge document variation %s", documentVariationId)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Error deleting knowledge document variation %s | error: %s", documentVariationId, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_document_variation", fmt.Sprintf("Knowledge document variation %s still exists", documentVariationId), resp))
})
}
func buildDocumentContentListBlocks(blocksIn map[string]interface{}) *[]platformclientv2.Documentlistcontentblock {
if documentContentBlocks := blocksIn["blocks"].([]interface{}); documentContentBlocks != nil && len(documentContentBlocks) > 0 {
blocksOut := make([]platformclientv2.Documentlistcontentblock, 0)
for _, block := range documentContentBlocks {
blockMap := block.(map[string]interface{})
varType := blockMap["type"].(string)
blockOut := platformclientv2.Documentlistcontentblock{
VarType: &varType,
Text: buildDocumentText(blockMap),
Image: buildDocumentImage(blockMap),
}
blocksOut = append(blocksOut, blockOut)
}
return &blocksOut
}
return nil
}
func buildDocumentContentBlocks(blocksIn map[string]interface{}) *[]platformclientv2.Documentcontentblock {
if documentContentBlocks := blocksIn["blocks"].([]interface{}); documentContentBlocks != nil && len(documentContentBlocks) > 0 {
blocksOut := make([]platformclientv2.Documentcontentblock, 0)
for _, block := range documentContentBlocks {
blockMap := block.(map[string]interface{})
varType := blockMap["type"].(string)
blockOut := platformclientv2.Documentcontentblock{
VarType: &varType,
Text: buildDocumentText(blockMap),
Image: buildDocumentImage(blockMap),
}
blocksOut = append(blocksOut, blockOut)
}
return &blocksOut
}
return nil
}
func buildDocumentListBlocks(blocksIn map[string]interface{}) *[]platformclientv2.Documentbodylistblock {
if documentListBlocks := blocksIn["blocks"].([]interface{}); documentListBlocks != nil && len(documentListBlocks) > 0 {
blocksOut := make([]platformclientv2.Documentbodylistblock, 0)
for _, block := range documentListBlocks {
blockMap := block.(map[string]interface{})
varType := blockMap["type"].(string)
blockOut := platformclientv2.Documentbodylistblock{
VarType: &varType,
Blocks: buildDocumentContentListBlocks(blockMap),
}
blocksOut = append(blocksOut, blockOut)
}
return &blocksOut
}
return nil
}
func buildDocumentText(textIn map[string]interface{}) *platformclientv2.Documenttext {
if textList := textIn["text"].([]interface{}); textList != nil && len(textList) > 0 {
text := textList[0].(map[string]interface{})
textString := text["text"].(string)
textOut := platformclientv2.Documenttext{
Text: &textString,
}
if marks, ok := text["marks"].(*schema.Set); ok {
markArr := lists.SetToStringList(marks)
textOut.Marks = markArr
}
if hyperlink, ok := text["hyperlink"].(string); ok {
if len(hyperlink) > 0 {
textOut.Hyperlink = &hyperlink
}
}
return &textOut
}
return nil
}
func buildDocumentParagraph(paragraphIn map[string]interface{}) *platformclientv2.Documentbodyparagraph {
if paragraphList := paragraphIn["paragraph"].([]interface{}); paragraphList != nil && len(paragraphList) > 0 {
paragraph := paragraphList[0].(map[string]interface{})
paragraphOut := platformclientv2.Documentbodyparagraph{
Blocks: buildDocumentContentBlocks(paragraph),
}
return ¶graphOut
}
return nil
}
func buildDocumentImage(imageIn map[string]interface{}) *platformclientv2.Documentbodyimage {
if imageList := imageIn["image"].([]interface{}); imageList != nil && len(imageList) > 0 {
image := imageList[0].(map[string]interface{})
url := image["url"].(string)
imageOut := platformclientv2.Documentbodyimage{
Url: &url,
}
if hyperlink, ok := image["hyperlink"].(string); ok {
if len(hyperlink) > 0 {
imageOut.Hyperlink = &hyperlink
}
}
return &imageOut
}
return nil
}
func buildDocumentVideo(videoIn map[string]interface{}) *platformclientv2.Documentbodyvideo {
if videoList := videoIn["video"].([]interface{}); videoList != nil && len(videoList) > 0 {
video := videoList[0].(map[string]interface{})
url := video["url"].(string)
videoOut := platformclientv2.Documentbodyvideo{
Url: &url,
}
return &videoOut
}
return nil
}
func buildDocumentList(listIn map[string]interface{}) *platformclientv2.Documentbodylist {
if listList := listIn["list"].([]interface{}); listList != nil && len(listList) > 0 {
list := listList[0].(map[string]interface{})
listOut := platformclientv2.Documentbodylist{
Blocks: buildDocumentListBlocks(list),
}
return &listOut
}
return nil
}
func buildDocumentBodyBlocks(blocksIn map[string]interface{}) *[]platformclientv2.Documentbodyblock {
if documentBodyBlocks := blocksIn["blocks"].([]interface{}); documentBodyBlocks != nil && len(documentBodyBlocks) > 0 {
blocksOut := make([]platformclientv2.Documentbodyblock, 0)
for _, block := range documentBodyBlocks {
blockMap := block.(map[string]interface{})
varType := blockMap["type"].(string)
blockOut := platformclientv2.Documentbodyblock{
VarType: &varType,
Paragraph: buildDocumentParagraph(blockMap),
Image: buildDocumentImage(blockMap),
Video: buildDocumentVideo(blockMap),
List: buildDocumentList(blockMap),
}
blocksOut = append(blocksOut, blockOut)
}
return &blocksOut
}
return nil
}
func buildVariationBody(bodyIn map[string]interface{}) *platformclientv2.Documentbody {
if bodyList := bodyIn["body"].([]interface{}); bodyList != nil && len(bodyList) > 0 {
variationBody := bodyList[0].(map[string]interface{})
bodyOut := platformclientv2.Documentbody{
Blocks: buildDocumentBodyBlocks(variationBody),
}
return &bodyOut
}
return nil
}
func buildKnowledgeDocumentVariation(variationIn map[string]interface{}) *platformclientv2.Documentvariation {
variationOut := platformclientv2.Documentvariation{
Body: buildVariationBody(variationIn),
}
return &variationOut
}
func buildKnowledgeDocumentVariationUpdate(variationIn map[string]interface{}) *platformclientv2.Documentvariation {
variationOut := platformclientv2.Documentvariation{
Body: buildVariationBody(variationIn),
}
return &variationOut
}
func flattenDocumentText(textIn platformclientv2.Documenttext) []interface{} {
textOut := make(map[string]interface{})
if textIn.Text != nil {
textOut["text"] = *textIn.Text
}
if textIn.Marks != nil {
markSet := lists.StringListToSet(*textIn.Marks)
textOut["marks"] = markSet
}
if textIn.Hyperlink != nil && len(*textIn.Hyperlink) > 0 {
textOut["hyperlink"] = *textIn.Hyperlink
}
return []interface{}{textOut}
}
func flattenDocumentContentListBlocks(blocksIn []platformclientv2.Documentlistcontentblock) []interface{} {
if len(blocksIn) == 0 {
return nil
}
blocksOut := make([]interface{}, 0)
for _, block := range blocksIn {
blockOutMap := make(map[string]interface{})
if block.VarType != nil {
blockOutMap["type"] = *block.VarType
}
if block.Text != nil {
blockOutMap["text"] = flattenDocumentText(*block.Text)
}
if block.Image != nil {
blockOutMap["image"] = flattenDocumentImage(*block.Image)
}
blocksOut = append(blocksOut, blockOutMap)
}
return blocksOut
}
func flattenDocumentContentBlocks(blocksIn []platformclientv2.Documentcontentblock) []interface{} {
if len(blocksIn) == 0 {
return nil
}
blocksOut := make([]interface{}, 0)
for _, block := range blocksIn {
blockOutMap := make(map[string]interface{})
if block.VarType != nil {
blockOutMap["type"] = *block.VarType
}
if block.Text != nil {
blockOutMap["text"] = flattenDocumentText(*block.Text)
}
if block.Image != nil {
blockOutMap["image"] = flattenDocumentImage(*block.Image)
}
blocksOut = append(blocksOut, blockOutMap)
}
return blocksOut
}
func flattenDocumentListBlocks(blocksIn []platformclientv2.Documentbodylistblock) []interface{} {
if len(blocksIn) == 0 {
return nil
}
blocksOut := make([]interface{}, 0)
for _, block := range blocksIn {
blockOutMap := make(map[string]interface{})
if block.VarType != nil {
blockOutMap["type"] = *block.VarType
}
if block.Blocks != nil {
blockOutMap["blocks"] = flattenDocumentContentListBlocks(*block.Blocks)
}
blocksOut = append(blocksOut, blockOutMap)
}
return blocksOut
}
func flattenDocumentParagraph(paragraphIn platformclientv2.Documentbodyparagraph) []interface{} {
paragraphOut := make(map[string]interface{})
if paragraphIn.Blocks != nil {
paragraphOut["blocks"] = flattenDocumentContentBlocks(*paragraphIn.Blocks)
}
return []interface{}{paragraphOut}
}
func flattenDocumentImage(imageIn platformclientv2.Documentbodyimage) []interface{} {
imageOut := make(map[string]interface{})
if imageIn.Url != nil {
imageOut["url"] = *imageIn.Url
}
if imageIn.Hyperlink != nil && len(*imageIn.Hyperlink) > 0 {
imageOut["hyperlink"] = *imageIn.Hyperlink
}
return []interface{}{imageOut}
}
func flattenDocumentVideo(imageIn platformclientv2.Documentbodyvideo) []interface{} {
imageOut := make(map[string]interface{})
if imageIn.Url != nil {
imageOut["url"] = *imageIn.Url
}
return []interface{}{imageOut}
}
func flattenDocumentList(listIn platformclientv2.Documentbodylist) []interface{} {
listOut := make(map[string]interface{})
if listIn.Blocks != nil {
listOut["blocks"] = flattenDocumentListBlocks(*listIn.Blocks)
}
return []interface{}{listOut}
}
func flattenDocumentBodyBlocks(blocksIn []platformclientv2.Documentbodyblock) []interface{} {
if len(blocksIn) == 0 {
return nil
}
blocksOut := make([]interface{}, 0)
for _, block := range blocksIn {
blockOutMap := make(map[string]interface{})
if block.VarType != nil {
blockOutMap["type"] = *block.VarType
}
if block.Paragraph != nil {
blockOutMap["paragraph"] = flattenDocumentParagraph(*block.Paragraph)
}
if block.Image != nil {
blockOutMap["image"] = flattenDocumentImage(*block.Image)
}
if block.Video != nil {
blockOutMap["video"] = flattenDocumentVideo(*block.Video)
}
if block.List != nil {
blockOutMap["list"] = flattenDocumentList(*block.List)
}
blocksOut = append(blocksOut, blockOutMap)
}
return blocksOut
}
func flattenVariationBody(bodyIn platformclientv2.Documentbody) []interface{} {
bodyOut := make(map[string]interface{})
if bodyIn.Blocks != nil {
bodyOut["blocks"] = flattenDocumentBodyBlocks(*bodyIn.Blocks)
}
return []interface{}{bodyOut}
}
func flattenDocumentVersion(versionIn platformclientv2.Addressableentityref) []interface{} {
versionOut := make(map[string]interface{})
if versionIn.Id != nil {
versionOut["id"] = *versionIn.Id
}
return []interface{}{versionOut}
}
func flattenKnowledgeDocumentVariation(variationIn platformclientv2.Documentvariation) []interface{} {
variationOut := make(map[string]interface{})
if variationIn.Body != nil {
variationOut["body"] = flattenVariationBody(*variationIn.Body)
}
if variationIn.DocumentVersion != nil {
variationOut["document_version"] = flattenDocumentVersion(*variationIn.DocumentVersion)
}
return []interface{}{variationOut}
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllKnowledgeKnowledgebases(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(clientConfig)
publishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, true)
if err != nil {
return nil, err
}
for _, knowledgeBase := range *publishedEntities {
resources[*knowledgeBase.Id] = &resourceExporter.ResourceMeta{Name: *knowledgeBase.Name}
}
unpublishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, false)
if err != nil {
return nil, err
}
for _, knowledgeBase := range *unpublishedEntities {
resources[*knowledgeBase.Id] = &resourceExporter.ResourceMeta{Name: *knowledgeBase.Name}
}
return resources, nil
}
func getAllKnowledgebaseEntities(knowledgeApi platformclientv2.KnowledgeApi, published bool) (*[]platformclientv2.Knowledgebase, diag.Diagnostics) {
var (
after string
entities []platformclientv2.Knowledgebase
)
const pageSize = 100
for {
knowledgeBases, resp, getErr := knowledgeApi.GetKnowledgeKnowledgebases("", after, "", fmt.Sprintf("%v", pageSize), "", "", published, "", "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to get page of knowledge bases error: %s", getErr), resp)
}
if knowledgeBases.Entities == nil || len(*knowledgeBases.Entities) == 0 {
break
}
entities = append(entities, *knowledgeBases.Entities...)
if knowledgeBases.NextUri == nil || *knowledgeBases.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*knowledgeBases.NextUri, "after")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to parse after cursor from knowledge base nextUri"), err)
}
if after == "" {
break
}
}
return &entities, nil
}
func KnowledgeKnowledgebaseExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllKnowledgeKnowledgebases),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceKnowledgeKnowledgebase() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Knowledge Base",
CreateContext: provider.CreateWithPooledClient(createKnowledgeKnowledgebase),
ReadContext: provider.ReadWithPooledClient(readKnowledgeKnowledgebase),
UpdateContext: provider.UpdateWithPooledClient(updateKnowledgeKnowledgebase),
DeleteContext: provider.DeleteWithPooledClient(deleteKnowledgeKnowledgebase),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Knowledge base name",
Type: schema.TypeString,
Optional: true,
},
"description": {
Description: "Knowledge base description",
Type: schema.TypeString,
Optional: true,
},
"core_language": {
Description: "Core language for knowledge base in which initial content must be created, language codes [en-US, en-UK, en-AU, de-DE] are supported currently, however the new DX knowledge will support all these language codes",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"en-US", "en-UK", "en-AU", "de-DE", "es-US", "es-ES", "fr-FR", "pt-BR", "nl-NL", "it-IT", "fr-CA"}, false),
},
"published": {
Description: "Flag that indicates the knowledge base is published",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
},
}
}
func createKnowledgeKnowledgebase(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
coreLanguage := d.Get("core_language").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Creating knowledge base %s", name)
knowledgeBase, resp, err := knowledgeAPI.PostKnowledgeKnowledgebases(platformclientv2.Knowledgebasecreaterequest{
Name: &name,
Description: &description,
CoreLanguage: &coreLanguage,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to create knowledge base %s error: %s", name, err), resp)
}
d.SetId(*knowledgeBase.Id)
log.Printf("Created knowledge base %s %s", name, *knowledgeBase.Id)
return readKnowledgeKnowledgebase(ctx, d, meta)
}
func readKnowledgeKnowledgebase(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceKnowledgeKnowledgebase(), constants.DefaultConsistencyChecks, "genesyscloud_knowledge_knowledgebase")
log.Printf("Reading knowledge base %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
knowledgeBase, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebase(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to read knowledge base %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to read knowledge base %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", knowledgeBase.Name)
resourcedata.SetNillableValue(d, "description", knowledgeBase.Description)
resourcedata.SetNillableValue(d, "core_language", knowledgeBase.CoreLanguage)
resourcedata.SetNillableValue(d, "published", knowledgeBase.Published)
log.Printf("Read knowledge base %s %s", d.Id(), *knowledgeBase.Name)
return cc.CheckState(d)
})
}
func updateKnowledgeKnowledgebase(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Updating knowledge base %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current knowledge base version
_, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebase(d.Id())
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to read knowledge base %s error: %s", name, getErr), resp)
}
update := platformclientv2.Knowledgebaseupdaterequest{
Name: &name,
Description: &description,
}
log.Printf("Updating knowledge base %s", name)
_, resp, putErr := knowledgeAPI.PatchKnowledgeKnowledgebase(d.Id(), update)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to update knowledge base %s error: %s", name, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated knowledge base %s %s", name, d.Id())
return readKnowledgeKnowledgebase(ctx, d, meta)
}
func deleteKnowledgeKnowledgebase(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Deleting knowledge base %s", name)
_, resp, err := knowledgeAPI.DeleteKnowledgeKnowledgebase(d.Id())
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Failed to delete knowledge base %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := knowledgeAPI.GetKnowledgeKnowledgebase(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Knowledge base deleted
log.Printf("Deleted Knowledge base %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Error deleting knowledge base %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_knowledgebase", fmt.Sprintf("Knowledge base %s still exists", d.Id()), resp))
})
}
func GenerateKnowledgeKnowledgebaseResource(
resourceID string,
name string,
description string,
coreLanguage string) string {
return fmt.Sprintf(`resource "genesyscloud_knowledge_knowledgebase" "%s" {
name = "%s"
description = "%s"
core_language = "%s"
}
`, resourceID, name, description, coreLanguage)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
knowledgeLabel = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the label.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"color": {
Description: "The color for the label.",
Type: schema.TypeString,
Required: true,
},
},
}
)
func getAllKnowledgeLabels(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
knowledgeBaseList := make([]platformclientv2.Knowledgebase, 0)
resources := make(resourceExporter.ResourceIDMetaMap)
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(clientConfig)
// get published knowledge bases
publishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, true)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *publishedEntities...)
// get unpublished knowledge bases
unpublishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, false)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *unpublishedEntities...)
for _, knowledgeBase := range knowledgeBaseList {
labelEntities, err := getAllKnowledgeLabelEntities(*knowledgeAPI, &knowledgeBase)
if err != nil {
return nil, err
}
for _, knowledgeLabel := range *labelEntities {
id := fmt.Sprintf("%s,%s", *knowledgeLabel.Id, *knowledgeBase.Id)
resources[id] = &resourceExporter.ResourceMeta{Name: *knowledgeLabel.Name}
}
}
return resources, nil
}
func getAllKnowledgeLabelEntities(knowledgeAPI platformclientv2.KnowledgeApi, knowledgeBase *platformclientv2.Knowledgebase) (*[]platformclientv2.Labelresponse, diag.Diagnostics) {
var (
after string
entities []platformclientv2.Labelresponse
)
const pageSize = 100
for i := 0; ; i++ {
knowledgeLabels, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLabels(*knowledgeBase.Id, "", after, fmt.Sprintf("%v", pageSize), "", false)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to get knowledge labels error: %s", getErr), resp)
}
if knowledgeLabels.Entities == nil || len(*knowledgeLabels.Entities) == 0 {
break
}
entities = append(entities, *knowledgeLabels.Entities...)
if knowledgeLabels.NextUri == nil || *knowledgeLabels.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*knowledgeLabels.NextUri, "after")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to parse after cursor from knowledge label nextUri"), err)
}
if after == "" {
break
}
}
return &entities, nil
}
func KnowledgeLabelExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllKnowledgeLabels),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"knowledge_base_id": {RefType: "genesyscloud_knowledge_knowledgebase"},
},
}
}
func ResourceKnowledgeLabel() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Knowledge Label",
CreateContext: provider.CreateWithPooledClient(createKnowledgeLabel),
ReadContext: provider.ReadWithPooledClient(readKnowledgeLabel),
UpdateContext: provider.UpdateWithPooledClient(updateKnowledgeLabel),
DeleteContext: provider.DeleteWithPooledClient(deleteKnowledgeLabel),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"knowledge_base_id": {
Description: "Knowledge base id of the label",
Type: schema.TypeString,
Required: true,
},
"knowledge_label": {
Description: "Knowledge label id",
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: knowledgeLabel,
},
},
}
}
func createKnowledgeLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
knowledgeBaseId := d.Get("knowledge_base_id").(string)
knowledgeLabel := d.Get("knowledge_label").([]interface{})[0].(map[string]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
knowledgeLabelRequest := buildKnowledgeLabel(knowledgeLabel)
log.Printf("Creating knowledge label %s", knowledgeLabel["name"].(string))
knowledgeLabelResponse, resp, err := knowledgeAPI.PostKnowledgeKnowledgebaseLabels(knowledgeBaseId, knowledgeLabelRequest)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to create knowledge label %s error: %s", knowledgeBaseId, err), resp)
}
id := fmt.Sprintf("%s,%s", *knowledgeLabelResponse.Id, knowledgeBaseId)
d.SetId(id)
log.Printf("Created knowledge label %s", *knowledgeLabelResponse.Id)
return readKnowledgeLabel(ctx, d, meta)
}
func readKnowledgeLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeLabelId := id[0]
knowledgeBaseId := id[1]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceKnowledgeLabel(), constants.DefaultConsistencyChecks, "genesyscloud_knowledge_label")
log.Printf("Reading knowledge label %s", knowledgeLabelId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
knowledgeLabel, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLabel(knowledgeBaseId, knowledgeLabelId)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to read knowledge label %s | error: %s", knowledgeLabelId, getErr), resp))
}
log.Printf("%s", getErr)
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to read knowledge label %s | error: %s", knowledgeLabelId, getErr), resp))
}
newId := fmt.Sprintf("%s,%s", *knowledgeLabel.Id, knowledgeBaseId)
d.SetId(newId)
d.Set("knowledge_base_id", knowledgeBaseId)
d.Set("knowledge_label", flattenKnowledgeLabel(knowledgeLabel))
log.Printf("Read knowledge label %s", knowledgeLabelId)
return cc.CheckState(d)
})
}
func updateKnowledgeLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeLabelId := id[0]
knowledgeBaseId := id[1]
knowledgeLabel := d.Get("knowledge_label").([]interface{})[0].(map[string]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Updating knowledge label %s", knowledgeLabel["name"].(string))
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current knowledge label version
_, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLabel(knowledgeBaseId, knowledgeLabelId)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to read knowledge label %s error: %s", knowledgeLabelId, getErr), resp)
}
knowledgeLabelUpdate := buildKnowledgeLabelUpdate(knowledgeLabel)
log.Printf("Updating knowledge label %s", knowledgeLabel["name"].(string))
_, resp, putErr := knowledgeAPI.PatchKnowledgeKnowledgebaseLabel(knowledgeBaseId, knowledgeLabelId, knowledgeLabelUpdate)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to update knowledge label %s error: %s", knowledgeLabelId, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated knowledge label %s %s", knowledgeLabel["name"].(string), knowledgeLabelId)
return readKnowledgeLabel(ctx, d, meta)
}
func deleteKnowledgeLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), ",")
knowledgeLabelId := id[0]
knowledgeBaseId := id[1]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Deleting knowledge label %s", id)
_, resp, err := knowledgeAPI.DeleteKnowledgeKnowledgebaseLabel(knowledgeBaseId, knowledgeLabelId)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Failed to delete knowledge label %s error: %s", id, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := knowledgeAPI.GetKnowledgeKnowledgebaseLabel(knowledgeBaseId, knowledgeLabelId)
if err != nil {
if util.IsStatus404(resp) {
// Knowledge label deleted
log.Printf("Deleted knowledge label %s", knowledgeLabelId)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Error deleting knowledge label %s | error: %s", knowledgeLabelId, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_label", fmt.Sprintf("Knowledge label %s still exists", knowledgeLabelId), resp))
})
}
func buildKnowledgeLabel(labelIn map[string]interface{}) platformclientv2.Labelcreaterequest {
name := labelIn["name"].(string)
color := labelIn["color"].(string)
labelOut := platformclientv2.Labelcreaterequest{
Name: &name,
Color: &color,
}
return labelOut
}
func buildKnowledgeLabelUpdate(labelIn map[string]interface{}) platformclientv2.Labelupdaterequest {
name := labelIn["name"].(string)
color := labelIn["color"].(string)
labelOut := platformclientv2.Labelupdaterequest{
Name: &name,
Color: &color,
}
return labelOut
}
func flattenKnowledgeLabel(labelIn *platformclientv2.Labelresponse) []interface{} {
labelOut := make(map[string]interface{})
if labelIn.Name != nil {
labelOut["name"] = *labelIn.Name
}
if labelIn.Color != nil {
labelOut["color"] = *labelIn.Color
}
return []interface{}{labelOut}
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
knowledgeCategoryV1 = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Knowledge base name. Changing the name attribute will cause the knowledge_category resource to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"description": {
Description: "Knowledge base description",
Type: schema.TypeString,
Optional: true,
},
"parent_id": {
Description: "Knowledge category parent id",
Type: schema.TypeString,
Optional: true,
},
},
}
)
func getAllKnowledgeCategoriesV1(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
knowledgeBaseList := make([]platformclientv2.Knowledgebase, 0)
categoryEntities := make([]platformclientv2.Knowledgecategory, 0)
resources := make(resourceExporter.ResourceIDMetaMap)
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(clientConfig)
// get published knowledge bases
publishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, true)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *publishedEntities...)
// get unpublished knowledge bases
unpublishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, false)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *unpublishedEntities...)
for _, knowledgeBase := range knowledgeBaseList {
partialEntities, err := getAllKnowledgeV1CategoryEntities(*knowledgeAPI, &knowledgeBase)
if err != nil {
return nil, err
}
categoryEntities = append(categoryEntities, *partialEntities...)
}
for _, knowledgeCategory := range categoryEntities {
id := fmt.Sprintf("%s %s %s", *knowledgeCategory.Id, *knowledgeCategory.KnowledgeBase.Id, *knowledgeCategory.LanguageCode)
resources[id] = &resourceExporter.ResourceMeta{Name: *knowledgeCategory.Name}
}
return resources, nil
}
func getAllKnowledgeV1CategoryEntities(knowledgeAPI platformclientv2.KnowledgeApi, knowledgeBase *platformclientv2.Knowledgebase) (*[]platformclientv2.Knowledgecategory, diag.Diagnostics) {
var (
after string
entities []platformclientv2.Knowledgecategory
)
const pageSize = 100
for i := 0; ; i++ {
knowledgeCategories, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageCategories(*knowledgeBase.Id, *knowledgeBase.CoreLanguage, "", after, "", fmt.Sprintf("%v", pageSize), "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to get knowledge categorys error: %s", getErr), resp)
}
if knowledgeCategories.Entities == nil || len(*knowledgeCategories.Entities) == 0 {
break
}
entities = append(entities, *knowledgeCategories.Entities...)
if knowledgeCategories.NextUri == nil || *knowledgeCategories.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*knowledgeCategories.NextUri, "after")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to parse after cursor from knowledge category nextUri"), err)
}
if after == "" {
break
}
}
return &entities, nil
}
func KnowledgeCategoryExporterV1() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllKnowledgeCategoriesV1),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"knowledge_base_id": {RefType: "genesyscloud_knowledge_knowledgebase"},
},
}
}
func ResourceKnowledgeCategoryV1() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Knowledge v1 Category",
CreateContext: provider.CreateWithPooledClient(createKnowledgeCategoryV1),
ReadContext: provider.ReadWithPooledClient(readKnowledgeCategoryV1),
UpdateContext: provider.UpdateWithPooledClient(updateKnowledgeCategoryV1),
DeleteContext: provider.DeleteWithPooledClient(deleteKnowledgeCategoryV1),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"knowledge_base_id": {
Description: "Knowledge base id of the category",
Type: schema.TypeString,
Required: true,
},
"language_code": {
Description: "language code of the category",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"en-US", "en-UK", "en-AU", "de-DE", "es-US", "es-ES", "fr-FR", "pt-BR", "nl-NL", "it-IT", "fr-CA"}, false),
},
"knowledge_category": {
Description: "Knowledge category parent id",
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: knowledgeCategory,
},
},
}
}
func createKnowledgeCategoryV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
languageCode := d.Get("language_code").(string)
knowledgeBaseId := d.Get("knowledge_base_id").(string)
knowledgeCategory := d.Get("knowledge_category").([]interface{})[0].(map[string]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
knowledgeCategoryRequest := buildKnowledgeCategoryV1(knowledgeCategory)
log.Printf("Creating knowledge category %s", knowledgeCategory["name"].(string))
knowledgeCategoryResponse, resp, err := knowledgeAPI.PostKnowledgeKnowledgebaseLanguageCategories(knowledgeBaseId, languageCode, knowledgeCategoryRequest)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to create knowledge category %s error: %s", knowledgeBaseId, err), resp)
}
id := fmt.Sprintf("%s %s %s", *knowledgeCategoryResponse.Id, *knowledgeCategoryResponse.KnowledgeBase.Id, *knowledgeCategoryResponse.LanguageCode)
d.SetId(id)
log.Printf("Created knowledge category %s", *knowledgeCategoryResponse.Id)
return readKnowledgeCategoryV1(ctx, d, meta)
}
func readKnowledgeCategoryV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
knowledgeCategoryId := id[0]
knowledgeBaseId := id[1]
languageCode := id[2]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceKnowledgeCategory(), constants.DefaultConsistencyChecks, "genesyscloud_knowledge_v1_category")
log.Printf("Reading knowledge category %s", knowledgeCategoryId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
knowledgeCategory, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageCategory(knowledgeCategoryId, knowledgeBaseId, languageCode)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to read knowledge category %s | error: %s", knowledgeCategoryId, getErr), resp))
}
log.Printf("%s", getErr)
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to read knowledge category %s | error: %s", knowledgeCategoryId, getErr), resp))
}
newId := fmt.Sprintf("%s %s %s", *knowledgeCategory.Id, *knowledgeCategory.KnowledgeBase.Id, *knowledgeCategory.LanguageCode)
d.SetId(newId)
d.Set("knowledge_base_id", *knowledgeCategory.KnowledgeBase.Id)
d.Set("language_code", *knowledgeCategory.LanguageCode)
d.Set("knowledge_category", flattenKnowledgeCategoryV1(knowledgeCategory))
log.Printf("Read knowledge category %s", knowledgeCategoryId)
return cc.CheckState(d)
})
}
func updateKnowledgeCategoryV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
knowledgeCategoryId := id[0]
knowledgeBaseId := id[1]
languageCode := id[2]
knowledgeCategory := d.Get("knowledge_category").([]interface{})[0].(map[string]interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Updating knowledge category %s", knowledgeCategory["name"].(string))
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current knowledge category version
_, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageCategory(knowledgeCategoryId, knowledgeBaseId, languageCode)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to read knowledge category %s error: %s", knowledgeCategoryId, getErr), resp)
}
knowledgeCategoryUpdate := buildKnowledgeCategoryV1(knowledgeCategory)
log.Printf("Updating knowledge category %s", knowledgeCategory["name"].(string))
_, resp, putErr := knowledgeAPI.PatchKnowledgeKnowledgebaseLanguageCategory(knowledgeCategoryId, knowledgeBaseId, languageCode, knowledgeCategoryUpdate)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to update knowledge category %s error: %s", knowledgeCategoryId, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated knowledge category %s %s", knowledgeCategory["name"].(string), knowledgeCategoryId)
return readKnowledgeCategoryV1(ctx, d, meta)
}
func deleteKnowledgeCategoryV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
knowledgeCategoryId := id[0]
knowledgeBaseId := id[1]
languageCode := id[2]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Deleting knowledge category %s", id)
_, resp, err := knowledgeAPI.DeleteKnowledgeKnowledgebaseLanguageCategory(knowledgeCategoryId, knowledgeBaseId, languageCode)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Failed to delete knowledge category %s error: %s", id, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageCategory(knowledgeCategoryId, knowledgeBaseId, languageCode)
if err != nil {
if util.IsStatus404(resp) {
// Knowledge category deleted
log.Printf("Deleted knowledge category %s", knowledgeCategoryId)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Error deleting knowledge category %s | error: %s", knowledgeCategoryId, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_category", fmt.Sprintf("Knowledge category %s still exists", knowledgeCategoryId), resp))
})
}
func buildKnowledgeCategoryV1(categoryIn map[string]interface{}) platformclientv2.Knowledgecategoryrequest {
categoryOut := platformclientv2.Knowledgecategoryrequest{}
if name, ok := categoryIn["name"].(string); ok && name != "" {
categoryOut.Name = &name
}
if description, ok := categoryIn["description"].(string); ok && description != "" {
categoryOut.Description = &description
}
if parentId, ok := categoryIn["parent_id"].(string); ok && parentId != "" {
categoryOut.Parent = &platformclientv2.Documentcategoryinput{
Id: &parentId,
}
}
return categoryOut
}
func flattenKnowledgeCategoryV1(categoryIn *platformclientv2.Knowledgeextendedcategory) []interface{} {
categoryOut := make(map[string]interface{})
if categoryIn.Name != nil {
categoryOut["name"] = *categoryIn.Name
}
if categoryIn.Description != nil {
categoryOut["description"] = *categoryIn.Description
}
if categoryIn.Parent != nil && categoryIn.Parent.Id != nil {
categoryOut["parent_id"] = *categoryIn.Parent.Id
}
return []interface{}{categoryOut}
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
knowledgeDocumentV1 = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "Document type according to assigned template",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Faq", "Article"}, false),
},
"external_url": {
Description: "External Url to the document",
Type: schema.TypeString,
Optional: true,
},
"faq": {
Description: "Faq document details",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: documentFaq,
},
"categories": {
Description: "List of knowledge base category names for the document",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"article": {
Description: "Article details",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: documentArticle,
},
},
}
documentFaq = &schema.Resource{
Schema: map[string]*schema.Schema{
"question": {
Description: "The question for this FAQ",
Type: schema.TypeString,
Optional: true,
},
"answer": {
Description: "The answer for this FAQ",
Type: schema.TypeString,
Optional: true,
},
"alternatives": {
Description: "List of Alternative questions related to the answer which helps in improving the likelihood of a match to user query",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
documentArticle = &schema.Resource{
Schema: map[string]*schema.Schema{
"title": {
Description: "The title of the Article.",
Type: schema.TypeString,
Optional: true,
},
"content_location_url": {
Description: "Presigned URL to retrieve the document content.",
Type: schema.TypeString,
Optional: true,
},
"alternatives": {
Description: "List of Alternative questions related to the title which helps in improving the likelihood of a match to user query.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
func getAllKnowledgeDocumentsV1(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
knowledgeBaseList := make([]platformclientv2.Knowledgebase, 0)
documentEntities := make([]platformclientv2.Knowledgedocument, 0)
resources := make(resourceExporter.ResourceIDMetaMap)
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(clientConfig)
// get published knowledge bases
publishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, true)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *publishedEntities...)
// get unpublished knowledge bases
unpublishedEntities, err := getAllKnowledgebaseEntities(*knowledgeAPI, false)
if err != nil {
return nil, err
}
knowledgeBaseList = append(knowledgeBaseList, *unpublishedEntities...)
for _, knowledgeBase := range knowledgeBaseList {
partialEntities, err := getAllKnowledgeV1DocumentEntities(*knowledgeAPI, &knowledgeBase)
if err != nil {
return nil, err
}
documentEntities = append(documentEntities, *partialEntities...)
}
for _, knowledgeDocument := range documentEntities {
id := fmt.Sprintf("%s %s %s", *knowledgeDocument.Id, *knowledgeDocument.KnowledgeBase.Id, *knowledgeDocument.LanguageCode)
var name string
if knowledgeDocument.Name != nil {
name = *knowledgeDocument.Name
} else {
name = fmt.Sprintf("document " + uuid.NewString())
}
resources[id] = &resourceExporter.ResourceMeta{Name: name}
}
return resources, nil
}
func getAllKnowledgeV1DocumentEntities(knowledgeAPI platformclientv2.KnowledgeApi, knowledgeBase *platformclientv2.Knowledgebase) (*[]platformclientv2.Knowledgedocument, diag.Diagnostics) {
var (
after string
entities []platformclientv2.Knowledgedocument
)
const pageSize = 100
for {
knowledgeDocuments, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageDocuments(*knowledgeBase.Id, *knowledgeBase.CoreLanguage, "", after, "", fmt.Sprintf("%v", pageSize), "", "", "", "", nil)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to get page of knowledge documents error: %s", getErr), resp)
}
if knowledgeDocuments.Entities == nil || len(*knowledgeDocuments.Entities) == 0 {
break
}
entities = append(entities, *knowledgeDocuments.Entities...)
if knowledgeDocuments.NextUri == nil || *knowledgeDocuments.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*knowledgeDocuments.NextUri, "after")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to parse after cursor from knowledge document nextUri"), err)
}
if after == "" {
break
}
}
return &entities, nil
}
func KnowledgeDocumentExporterV1() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllKnowledgeDocumentsV1),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"knowledge_base_id": {RefType: "genesyscloud_knowledge_knowledgebase"},
},
}
}
func ResourceKnowledgeDocumentV1() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Knowledge document",
CreateContext: provider.CreateWithPooledClient(createKnowledgeDocumentV1),
ReadContext: provider.ReadWithPooledClient(readKnowledgeDocumentV1),
UpdateContext: provider.UpdateWithPooledClient(updateKnowledgeDocumentV1),
DeleteContext: provider.DeleteWithPooledClient(deleteKnowledgeDocumentV1),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"knowledge_base_id": {
Description: "Knowledge base id",
Type: schema.TypeString,
Required: true,
},
"language_code": {
Description: "Language code",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"en-US", "en-UK", "en-AU", "de-DE", "es-US", "es-ES", "fr-FR", "pt-BR", "nl-NL", "it-IT", "fr-CA"}, false),
},
"knowledge_document": {
Description: "Knowledge document request body",
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: knowledgeDocumentV1,
},
},
}
}
func buildFaq(requestBody map[string]interface{}) *platformclientv2.Documentfaq {
faqIn := requestBody["faq"]
if faqIn == nil || len(faqIn.([]interface{})) <= 0 {
return nil
}
faqOut := platformclientv2.Documentfaq{}
temp := faqIn.([]interface{})[0].(map[string]interface{})
if question, ok := temp["question"].(string); ok && question != "" {
faqOut.Question = &question
}
if answer, ok := temp["answer"].(string); ok && answer != "" {
faqOut.Answer = &answer
}
if alternatives, ok := temp["alternatives"].(*schema.Set); ok {
faqOut.Alternatives = lists.SetToStringList(alternatives)
}
return &faqOut
}
func buildCategories(requestBody map[string]interface{}, knowledgeAPI *platformclientv2.KnowledgeApi, knowledgeBaseId string, languageCode string) (*[]platformclientv2.Documentcategoryinput, diag.Diagnostics) {
if requestBody["categories"] == nil {
return nil, nil
}
categories := make([]platformclientv2.Documentcategoryinput, 0)
categoryList := lists.SetToStringList(requestBody["categories"].(*schema.Set))
for _, categoryName := range *categoryList {
pageSize := 100
knowledgeCategories, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageCategories(knowledgeBaseId, languageCode, "", "", "", fmt.Sprintf("%v", pageSize), categoryName)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to get page of knowledge categories error: %s", getErr), resp)
}
matchingCategory := (*knowledgeCategories.Entities)[0]
category := platformclientv2.Documentcategoryinput{
Id: matchingCategory.Id,
}
categories = append(categories, category)
}
return &categories, nil
}
func buildArticle(requestBody map[string]interface{}) *platformclientv2.Documentarticle {
articleIn := requestBody["article"]
if articleIn == nil || len(articleIn.([]interface{})) <= 0 {
return nil
}
articleOut := platformclientv2.Documentarticle{}
temp := articleIn.([]interface{})[0].(map[string]interface{})
if title, ok := temp["title"].(string); ok && title != "" {
articleOut.Title = &title
}
if contentLocationUrl, ok := temp["content_location_url"].(string); ok {
articleContentBody := platformclientv2.Articlecontentbody{
LocationUrl: &contentLocationUrl,
}
articleContent := platformclientv2.Articlecontent{
Body: &articleContentBody,
}
articleOut.Content = &articleContent
}
if alternatives, ok := temp["alternatives"].(*schema.Set); ok {
articleOut.Alternatives = lists.SetToStringList(alternatives)
}
return &articleOut
}
func buildKnowledgeDocumentRequestV1(d *schema.ResourceData, knowledgeAPI *platformclientv2.KnowledgeApi, knowledgeBaseId string, languageCode string) platformclientv2.Knowledgedocumentrequest {
requestIn := d.Get("knowledge_document").([]interface{})[0].(map[string]interface{})
categories, _ := buildCategories(requestIn, knowledgeAPI, knowledgeBaseId, languageCode)
requestOut := platformclientv2.Knowledgedocumentrequest{
Faq: buildFaq(requestIn),
Categories: categories,
Article: buildArticle(requestIn),
}
if externalUrl, ok := requestIn["external_url"].(string); ok && externalUrl != "" {
requestOut.ExternalUrl = &externalUrl
}
if varType, ok := requestIn["type"].(string); ok && varType != "" {
requestOut.VarType = &varType
}
return requestOut
}
func flattenKnowledgeDocumentV1(document *platformclientv2.Knowledgedocument) []interface{} {
if document == nil {
return nil
}
documentMap := make(map[string]interface{})
documentMap["categories"] = flattenCategories(document.Categories)
documentMap["faq"] = flattenFaq(document.Faq)
documentMap["article"] = flattenArticle(document.Article)
if document.VarType != nil {
documentMap["type"] = *document.VarType
}
if document.ExternalUrl != nil {
documentMap["external_url"] = *document.ExternalUrl
}
return []interface{}{documentMap}
}
func flattenFaq(faq *platformclientv2.Documentfaq) []interface{} {
if faq == nil {
return nil
}
faqMap := make(map[string]interface{})
if faq.Question != nil {
faqMap["question"] = faq.Question
}
if faq.Answer != nil {
faqMap["answer"] = faq.Answer
}
if faq.Alternatives != nil {
faqMap["alternatives"] = lists.StringListToSet(*faq.Alternatives)
}
return []interface{}{faqMap}
}
func flattenArticle(article *platformclientv2.Documentarticle) []interface{} {
if article == nil {
return nil
}
articleMap := make(map[string]interface{})
if article.Title != nil {
articleMap["title"] = article.Title
}
if article.Content != nil && article.Content.Body != nil && article.Content.Body.LocationUrl != nil {
articleMap["content_location_url"] = article.Content.Body.LocationUrl
}
if article.Alternatives != nil {
articleMap["alternatives"] = lists.StringListToSet(*article.Alternatives)
}
return []interface{}{articleMap}
}
func flattenCategories(categories *[]platformclientv2.Knowledgecategory) *schema.Set {
if categories == nil {
return nil
}
categoryList := make([]string, 0)
for _, category := range *categories {
if category.Id != nil {
categoryList = append(categoryList, *category.Name)
}
}
return lists.StringListToSet(categoryList)
}
func createKnowledgeDocumentV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
knowledgeBaseId := d.Get("knowledge_base_id").(string)
languageCode := d.Get("language_code").(string)
body := buildKnowledgeDocumentRequestV1(d, knowledgeAPI, knowledgeBaseId, languageCode)
log.Printf("Creating knowledge document")
knowledgeDocument, resp, err := knowledgeAPI.PostKnowledgeKnowledgebaseLanguageDocuments(knowledgeBaseId, languageCode, body)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to create knowledge document %s error: %s", d.Id(), err), resp)
}
id := fmt.Sprintf("%s %s %s", *knowledgeDocument.Id, *knowledgeDocument.KnowledgeBase.Id, *knowledgeDocument.LanguageCode)
d.SetId(id)
log.Printf("Created knowledge document %s", *knowledgeDocument.Id)
return readKnowledgeDocumentV1(ctx, d, meta)
}
func readKnowledgeDocumentV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
knowledgeDocumentId := id[0]
knowledgeBaseId := id[1]
languageCode := id[2]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceKnowledgeDocument(), constants.DefaultConsistencyChecks, "genesyscloud_knowledge_v1_document")
log.Printf("Reading knowledge document %s", knowledgeDocumentId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
knowledgeDocument, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageDocument(knowledgeDocumentId, knowledgeBaseId, languageCode)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to read knowledge document %s | error: %s", knowledgeDocumentId, getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to read knowledge document %s | error: %s", knowledgeDocumentId, getErr), resp))
}
// required
newId := fmt.Sprintf("%s %s %s", *knowledgeDocument.Id, *knowledgeDocument.KnowledgeBase.Id, *knowledgeDocument.LanguageCode)
d.SetId(newId)
d.Set("knowledge_base_id", *knowledgeDocument.KnowledgeBase.Id)
d.Set("language_code", *knowledgeDocument.LanguageCode)
d.Set("knowledge_document", flattenKnowledgeDocumentV1(knowledgeDocument))
log.Printf("Read Knowledge document %s", *knowledgeDocument.Id)
return cc.CheckState(d)
})
}
func updateKnowledgeDocumentV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
knowledgeDocumentId := id[0]
knowledgeBaseId := id[1]
languageCode := id[2]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Updating Knowledge document %s", d.Id())
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current Knowledge document version
_, resp, getErr := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageDocument(knowledgeDocumentId, knowledgeBaseId, languageCode)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to read knowledge document %s error: %s", knowledgeDocumentId, getErr), resp)
}
body := d.Get("knowledge_document").([]interface{})[0].(map[string]interface{})
update := platformclientv2.Knowledgedocumentrequest{
Faq: buildFaq(body),
Article: buildArticle(body),
}
categories, _ := buildCategories(body, knowledgeAPI, knowledgeBaseId, languageCode)
if categories != nil {
update.Categories = categories
}
if varType, ok := body["type"].(string); ok {
update.VarType = &varType
}
if externalUrl, ok := body["external_url"].(string); ok {
update.ExternalUrl = &externalUrl
}
log.Printf("Updating knowledge document %s", knowledgeDocumentId)
_, resp, putErr := knowledgeAPI.PatchKnowledgeKnowledgebaseLanguageDocument(knowledgeDocumentId, knowledgeBaseId, languageCode, update)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to update knowledge document %s error: %s", knowledgeDocumentId, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Knowledge document %s", knowledgeDocumentId)
return readKnowledgeDocumentV1(ctx, d, meta)
}
func deleteKnowledgeDocumentV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
id := strings.Split(d.Id(), " ")
knowledgeDocumentId := id[0]
knowledgeBaseId := id[1]
languageCode := id[2]
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
knowledgeAPI := platformclientv2.NewKnowledgeApiWithConfig(sdkConfig)
log.Printf("Deleting Knowledge document %s", knowledgeDocumentId)
_, resp, err := knowledgeAPI.DeleteKnowledgeKnowledgebaseLanguageDocument(knowledgeDocumentId, knowledgeBaseId, languageCode)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Failed to delete knowledge document %s error: %s", knowledgeDocumentId, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := knowledgeAPI.GetKnowledgeKnowledgebaseLanguageDocument(knowledgeDocumentId, knowledgeBaseId, languageCode)
if err != nil {
if util.IsStatus404(resp) {
// Knowledge document deleted
log.Printf("Deleted Knowledge document %s", knowledgeDocumentId)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Error deleting Knowledge document %s | error: %s", knowledgeDocumentId, err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_knowledge_v1_document", fmt.Sprintf("Knowledge document %s still exists", knowledgeDocumentId), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/validators"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"github.com/nyaruka/phonenumbers"
)
func getAllLocations(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
locationsAPI := platformclientv2.NewLocationsApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
locations, resp, getErr := locationsAPI.GetLocations(pageSize, pageNum, nil, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_location", fmt.Sprintf("Failed to get page of locations error: %s", getErr), resp)
}
if locations.Entities == nil || len(*locations.Entities) == 0 {
break
}
for _, location := range *locations.Entities {
resources[*location.Id] = &resourceExporter.ResourceMeta{Name: *location.Name}
}
}
return resources, nil
}
func LocationExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllLocations),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"path": {RefType: "genesyscloud_location"},
},
CustomValidateExports: map[string][]string{
"E164": {"emergency_number.number"},
},
}
}
func ResourceLocation() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Location",
CreateContext: provider.CreateWithPooledClient(createLocation),
ReadContext: provider.ReadWithPooledClient(readLocation),
UpdateContext: provider.UpdateWithPooledClient(updateLocation),
DeleteContext: provider.DeleteWithPooledClient(deleteLocation),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Location name.",
Type: schema.TypeString,
Required: true,
},
"path": {
Description: "A list of ancestor location IDs. This can be used to create sublocations.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"notes": {
Description: "Notes for this location.",
Type: schema.TypeString,
Optional: true,
},
"emergency_number": {
Description: "Emergency phone number for this location.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"number": {
Description: "Emergency phone number. Must be in an E.164 number format.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validators.ValidatePhoneNumber,
DiffSuppressFunc: comparePhoneNumbers,
},
"type": {
Description: "Type of emergency number (default | elin).",
Type: schema.TypeString,
Optional: true,
Default: "default",
ValidateFunc: validation.StringInSlice([]string{"default", "elin"}, false),
},
},
},
},
"address": {
Description: "Address for this location. This cannot be changed while an emergency number is assigned.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"city": {
Description: "Location city.",
Type: schema.TypeString,
Required: true,
},
"country": {
Description: "Country abbreviation.",
Type: schema.TypeString,
Required: true,
},
"state": {
Description: "Location state. Required for countries with states.",
Type: schema.TypeString,
Optional: true,
},
"street1": {
Description: "Street address 1.",
Type: schema.TypeString,
Required: true,
},
"street2": {
Description: "Street address 2.",
Type: schema.TypeString,
Optional: true,
},
"zip_code": {
Description: "Location zip code.",
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
}
func createLocation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
notes := d.Get("notes").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
locationsAPI := platformclientv2.NewLocationsApiWithConfig(sdkConfig)
create := platformclientv2.Locationcreatedefinition{
Name: &name,
Path: buildSdkLocationPath(d),
EmergencyNumber: buildSdkLocationEmergencyNumber(d),
Address: buildSdkLocationAddress(d),
}
if notes != "" {
// API does not let allow empty string for notes on create
create.Notes = ¬es
}
log.Printf("Creating location %s", name)
location, resp, err := locationsAPI.PostLocations(create)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_location", fmt.Sprintf("Failed to create location %s error: %s", name, err), resp)
}
d.SetId(*location.Id)
log.Printf("Created location %s %s", name, *location.Id)
return readLocation(ctx, d, meta)
}
func readLocation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
locationsAPI := platformclientv2.NewLocationsApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceLocation(), constants.DefaultConsistencyChecks, "genesyscloud_location")
log.Printf("Reading location %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
location, resp, getErr := locationsAPI.GetLocation(d.Id(), nil)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_location", fmt.Sprintf("Failed to read location %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_location", fmt.Sprintf("Failed to read location %s | error: %s", d.Id(), getErr), resp))
}
if location.State != nil && *location.State == "deleted" {
d.SetId("")
return nil
}
d.Set("name", *location.Name)
if location.Notes != nil {
d.Set("notes", *location.Notes)
} else {
d.Set("notes", nil)
}
if location.Path != nil {
d.Set("path", *location.Path)
} else {
d.Set("path", nil)
}
d.Set("emergency_number", flattenLocationEmergencyNumber(location.EmergencyNumber))
d.Set("address", flattenLocationAddress(location.Address))
log.Printf("Read location %s %s", d.Id(), *location.Name)
return cc.CheckState(d)
})
}
func updateLocation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
notes := d.Get("notes").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
locationsAPI := platformclientv2.NewLocationsApiWithConfig(sdkConfig)
log.Printf("Updating location %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current location version
location, resp, getErr := locationsAPI.GetLocation(d.Id(), nil)
if getErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_location", fmt.Sprintf("Failed to read location %s error: %s", name, getErr), resp)
}
update := platformclientv2.Locationupdatedefinition{
Version: location.Version,
Name: &name,
Path: buildSdkLocationPath(d),
EmergencyNumber: buildSdkLocationEmergencyNumber(d),
}
if d.HasChange("address") {
// Even if address is the same, the API does not allow it in the patch request if a number is assigned
update.Address = buildSdkLocationAddress(d)
}
if notes != "" {
update.Notes = ¬es
} else {
// nil will result in no change occurring, and an empty string is invalid for this field
filler := " "
update.Notes = &filler
err := d.Set("notes", filler)
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_location", fmt.Sprintf("error setting the value of 'notes' attribute"), err)
}
}
log.Printf("Updating location %s", name)
_, resp, putErr := locationsAPI.PatchLocation(d.Id(), update)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_location", fmt.Sprintf("Failed to update location %s error: %s", name, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated location %s %s", name, d.Id())
return readLocation(ctx, d, meta)
}
func deleteLocation(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
locationsAPI := platformclientv2.NewLocationsApiWithConfig(sdkConfig)
log.Printf("Deleting location %s", name)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Directory occasionally returns version errors on deletes if an object was updated at the same time.
resp, err := locationsAPI.DeleteLocation(d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_location", fmt.Sprintf("Failed to delete location %s error: %s", name, err), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
location, resp, err := locationsAPI.GetLocation(d.Id(), nil)
if err != nil {
if util.IsStatus404(resp) {
// Location deleted
log.Printf("Deleted location %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_location", fmt.Sprintf("Error deleting location %s | error: %s", d.Id(), err), resp))
}
if location.State != nil && *location.State == "deleted" {
// Location deleted
log.Printf("Deleted location %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_location", fmt.Sprintf("Location %s still exists", d.Id()), resp))
})
}
func buildSdkLocationPath(d *schema.ResourceData) *[]string {
path := []string{}
if pathConfig, ok := d.GetOk("path"); ok {
path = lists.InterfaceListToStrings(pathConfig.([]interface{}))
}
return &path
}
func buildSdkLocationEmergencyNumber(d *schema.ResourceData) *platformclientv2.Locationemergencynumber {
if numberConfig := d.Get("emergency_number"); numberConfig != nil {
if numberList := numberConfig.([]interface{}); len(numberList) > 0 {
settingsMap := numberList[0].(map[string]interface{})
number := settingsMap["number"].(string)
typeStr := settingsMap["type"].(string)
return &platformclientv2.Locationemergencynumber{
Number: &number,
VarType: &typeStr,
}
}
}
return &platformclientv2.Locationemergencynumber{}
}
func buildSdkLocationAddress(d *schema.ResourceData) *platformclientv2.Locationaddress {
if addressConfig := d.Get("address"); addressConfig != nil {
if addrList := addressConfig.([]interface{}); len(addrList) > 0 {
addrMap := addrList[0].(map[string]interface{})
city := addrMap["city"].(string)
country := addrMap["country"].(string)
zip := addrMap["zip_code"].(string)
street1 := addrMap["street1"].(string)
address := platformclientv2.Locationaddress{
City: &city,
Country: &country,
Zipcode: &zip,
Street1: &street1,
}
// Optional values
if state, ok := addrMap["state"]; ok {
stateStr := state.(string)
address.State = &stateStr
}
if street2, ok := addrMap["street2"]; ok {
street2Str := street2.(string)
address.Street2 = &street2Str
}
return &address
}
}
return &platformclientv2.Locationaddress{}
}
func flattenLocationEmergencyNumber(numberConfig *platformclientv2.Locationemergencynumber) []interface{} {
if numberConfig == nil {
return nil
}
numberSettings := make(map[string]interface{})
if numberConfig.Number != nil {
numberSettings["number"] = *numberConfig.Number
}
if numberConfig.VarType != nil {
numberSettings["type"] = *numberConfig.VarType
}
return []interface{}{numberSettings}
}
func flattenLocationAddress(addrConfig *platformclientv2.Locationaddress) []interface{} {
if addrConfig == nil {
return nil
}
addrSettings := make(map[string]interface{})
if addrConfig.City != nil {
addrSettings["city"] = *addrConfig.City
}
if addrConfig.Country != nil {
addrSettings["country"] = *addrConfig.Country
}
if addrConfig.State != nil {
addrSettings["state"] = *addrConfig.State
}
if addrConfig.Street1 != nil {
addrSettings["street1"] = *addrConfig.Street1
}
if addrConfig.Street2 != nil {
addrSettings["street2"] = *addrConfig.Street2
}
if addrConfig.Zipcode != nil {
addrSettings["zip_code"] = *addrConfig.Zipcode
}
return []interface{}{addrSettings}
}
func comparePhoneNumbers(_, old, new string, _ *schema.ResourceData) bool {
oldNum, err := phonenumbers.Parse(old, "US")
if err != nil {
return old == new
}
newNum, err := phonenumbers.Parse(new, "US")
if err != nil {
return old == new
}
return phonenumbers.IsNumberMatchWithNumbers(oldNum, newNum) == phonenumbers.EXACT_MATCH
}
func GenerateLocationResourceBasic(
resourceID,
name string,
nestedBlocks ...string) string {
return GenerateLocationResource(resourceID, name, "", []string{})
}
func GenerateLocationResource(
resourceID,
name,
notes string,
paths []string,
nestedBlocks ...string) string {
return fmt.Sprintf(`resource "genesyscloud_location" "%s" {
name = "%s"
notes = "%s"
path = [%s]
%s
}
`, resourceID, name, notes, strings.Join(paths, ","), strings.Join(nestedBlocks, "\n"))
}
func GenerateLocationEmergencyNum(number, typeStr string) string {
return fmt.Sprintf(`emergency_number {
number = "%s"
type = %s
}
`, number, typeStr)
}
func GenerateLocationAddress(street1, city, state, country, zip string) string {
return fmt.Sprintf(`address {
street1 = "%s"
city = "%s"
state = "%s"
country = "%s"
zip_code = "%s"
}
`, street1, city, state, country, zip)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strconv"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/tfexporter_state"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
evaluationFormQuestionGroup = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of display question in question group.",
Type: schema.TypeString,
Required: true,
},
"default_answers_to_highest": {
Description: "Specifies whether to default answers to highest score.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"default_answers_to_na": {
Description: "Specifies whether to default answers to not applicable.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"na_enabled": {
Description: "Specifies whether a not applicable answer is enabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"weight": {
Description: "Points per question",
Type: schema.TypeFloat,
Required: true,
},
"manual_weight": {
Description: "Specifies whether a manual weight is set.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"questions": {
Description: "Questions inside the group",
Type: schema.TypeList,
Required: true,
MinItems: 1,
Elem: evaluationFormQuestion,
},
"visibility_condition": {
Description: "Defines conditions where question would be visible",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: evaluationFormVisibilityCondition,
},
},
}
evaluationFormQuestion = &schema.Resource{
Schema: map[string]*schema.Schema{
"text": {
Description: "Individual question",
Type: schema.TypeString,
Required: true,
},
"help_text": {
Description: "Help text for the question.",
Type: schema.TypeString,
Optional: true,
},
"na_enabled": {
Description: "Specifies whether a not applicable answer is enabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"comments_required": {
Description: "Specifies whether comments are required.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"visibility_condition": {
Description: "Defines conditions where question would be visible",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: evaluationFormVisibilityCondition,
},
"answer_options": {
Description: "Options from which to choose an answer for this question.",
Type: schema.TypeList,
Required: true,
MinItems: 2,
Elem: evaluationFormAnswerOptions,
},
"is_kill": {
Description: "True if the question is a fatal question",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"is_critical": {
Description: "True if the question is a critical question",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
evaluationFormVisibilityCondition = &schema.Resource{
Schema: map[string]*schema.Schema{
"combining_operation": {
Description: "Valid Values: AND, OR",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"AND", "OR"}, false),
},
"predicates": {
Description: "A list of strings, each representing the location in the form of the Answer Option to depend on. In the format of \"/form/questionGroup/{questionGroupIndex}/question/{questionIndex}/answer/{answerIndex}\" or, to assume the current question group, \"../question/{questionIndex}/answer/{answerIndex}\". Note: Indexes are zero-based",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
evaluationFormAnswerOptions = &schema.Resource{
Schema: map[string]*schema.Schema{
"text": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeInt,
Required: true,
},
},
}
)
func getAllEvaluationForms(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
qualityAPI := platformclientv2.NewQualityApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
evaluationForms, resp, getErr := qualityAPI.GetQualityFormsEvaluations(pageSize, pageNum, "", "", "", "", "", "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to get page of evaluation forms error: %s", getErr), resp)
}
if evaluationForms.Entities == nil || len(*evaluationForms.Entities) == 0 {
break
}
for _, evaluationForm := range *evaluationForms.Entities {
resources[*evaluationForm.Id] = &resourceExporter.ResourceMeta{Name: *evaluationForm.Name}
}
}
return resources, nil
}
func EvaluationFormExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllEvaluationForms),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
AllowZeroValues: []string{"question_groups.questions.answer_options.value", "question_groups.weight"},
}
}
func ResourceEvaluationForm() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Evaluation Forms",
CreateContext: provider.CreateWithPooledClient(createEvaluationForm),
ReadContext: provider.ReadWithPooledClient(readEvaluationForm),
UpdateContext: provider.UpdateWithPooledClient(updateEvaluationForm),
DeleteContext: provider.DeleteWithPooledClient(deleteEvaluationForm),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"published": {
Description: "Specifies if the evalutaion form is published.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"question_groups": {
Description: "A list of question groups.",
Type: schema.TypeList,
Required: true,
MinItems: 1,
Elem: evaluationFormQuestionGroup,
},
},
}
}
func createEvaluationForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
published := d.Get("published").(bool)
questionGroups, qgErr := buildSdkQuestionGroups(d)
if qgErr != nil {
return qgErr
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
log.Printf("Creating Evaluation Form %s", name)
form, resp, err := qualityAPI.PostQualityFormsEvaluations(platformclientv2.Evaluationform{
Name: &name,
QuestionGroups: questionGroups,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to create evaluation form %s error: %s", name, err), resp)
}
// Make sure form is properly created
time.Sleep(2 * time.Second)
formId := form.Id
// Publishing
if published {
_, resp, err := qualityAPI.PostQualityPublishedformsEvaluations(platformclientv2.Publishform{
Id: formId,
Published: &published,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to publish evaluation form %s error: %s", name, err), resp)
}
}
d.SetId(*formId)
log.Printf("Created evaluation form %s %s", name, *form.Id)
return readEvaluationForm(ctx, d, meta)
}
func readEvaluationForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceEvaluationForm(), constants.DefaultConsistencyChecks, "genesyscloud_quality_forms_evaluation")
log.Printf("Reading evaluation form %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
evaluationForm, resp, getErr := qualityAPI.GetQualityFormsEvaluation(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to read evaluation form %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to read evaluation form %s | error: %s", d.Id(), getErr), resp))
}
// During an export, Retrieve a list of any published versions of the evaluation form
// If there are published versions, published will be set to true
if tfexporter_state.IsExporterActive() {
publishedVersions, resp, err := qualityAPI.GetQualityFormsEvaluationsBulkContexts([]string{*evaluationForm.ContextId})
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to retrieve a list of the latest published evaluation form versions"), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to retrieve a list of the latest published evaluation form versions"), resp))
}
if len(publishedVersions) > 0 {
_ = d.Set("published", true)
} else {
_ = d.Set("published", false)
}
} else {
_ = d.Set("published", *evaluationForm.Published)
}
if evaluationForm.Name != nil {
d.Set("name", *evaluationForm.Name)
}
if evaluationForm.QuestionGroups != nil {
d.Set("question_groups", flattenQuestionGroups(evaluationForm.QuestionGroups))
}
return cc.CheckState(d)
})
}
func updateEvaluationForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
published := d.Get("published").(bool)
questionGroups, qgErr := buildSdkQuestionGroups(d)
if qgErr != nil {
return qgErr
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
// Get the latest unpublished version of the form
formVersions, resp, err := qualityAPI.GetQualityFormsEvaluationVersions(d.Id(), 25, 1, "desc")
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to get evaluation form versions %s error: %s", name, err), resp)
}
unpublishedForm := (*formVersions.Entities)[0]
log.Printf("Updating Evaluation Form %s", name)
form, resp, err := qualityAPI.PutQualityFormsEvaluation(*unpublishedForm.Id, platformclientv2.Evaluationform{
Name: &name,
QuestionGroups: questionGroups,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to update evaluation form %s error: %s", name, err), resp)
}
// Set published property on evaluation form update.
if published {
_, resp, err := qualityAPI.PostQualityPublishedformsEvaluations(platformclientv2.Publishform{
Id: form.Id,
Published: &published,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to publish evaluation form %s error: %s", name, err), resp)
}
} else {
// If published property is reset to false, set the resource Id to the latest unpublished form
d.SetId(*form.Id)
}
log.Printf("Updated evaluation form %s %s", name, *form.Id)
return readEvaluationForm(ctx, d, meta)
}
func deleteEvaluationForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
// Get the latest unpublished version of the form
formVersions, resp, err := qualityAPI.GetQualityFormsEvaluationVersions(d.Id(), 25, 1, "desc")
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to get evaluation form versions %s error: %s", name, err), resp)
}
latestFormVersion := (*formVersions.Entities)[0]
d.SetId(*latestFormVersion.Id)
log.Printf("Deleting evaluation form %s", name)
if resp, err := qualityAPI.DeleteQualityFormsEvaluation(d.Id()); err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Failed to delete evaluation form %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := qualityAPI.GetQualityFormsEvaluation(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Evaluation form deleted
log.Printf("Deleted evaluation form %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluation", fmt.Sprintf("Error deleting evaluation form %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_evaluationn", fmt.Sprintf("Evaluation form %s still exists", d.Id()), resp))
})
}
func buildSdkQuestionGroups(d *schema.ResourceData) (*[]platformclientv2.Evaluationquestiongroup, diag.Diagnostics) {
questionGroupType := "questionGroup"
var evalQuestionGroups []platformclientv2.Evaluationquestiongroup
if questionGroups, ok := d.GetOk("question_groups"); ok {
questionGroupList := questionGroups.([]interface{})
for _, questionGroup := range questionGroupList {
questionGroupsMap := questionGroup.(map[string]interface{})
questionGroupName := questionGroupsMap["name"].(string)
defaultAnswersToHighest := questionGroupsMap["default_answers_to_highest"].(bool)
defaultAnswersToNA := questionGroupsMap["default_answers_to_na"].(bool)
naEnabled := questionGroupsMap["na_enabled"].(bool)
weight := float32(questionGroupsMap["weight"].(float64))
manualWeight := questionGroupsMap["manual_weight"].(bool)
questions := questionGroupsMap["questions"].([]interface{})
sdkquestionGroup := platformclientv2.Evaluationquestiongroup{
Name: &questionGroupName,
VarType: &questionGroupType,
DefaultAnswersToHighest: &defaultAnswersToHighest,
DefaultAnswersToNA: &defaultAnswersToNA,
NaEnabled: &naEnabled,
Weight: &weight,
ManualWeight: &manualWeight,
Questions: buildSdkQuestions(questions),
}
visibilityCondition := questionGroupsMap["visibility_condition"].([]interface{})
sdkquestionGroup.VisibilityCondition = buildSdkVisibilityCondition(visibilityCondition)
evalQuestionGroups = append(evalQuestionGroups, sdkquestionGroup)
}
}
return &evalQuestionGroups, nil
}
func buildSdkQuestions(questions []interface{}) *[]platformclientv2.Evaluationquestion {
questionType := "multipleChoiceQuestion"
sdkQuestions := make([]platformclientv2.Evaluationquestion, 0)
for _, question := range questions {
questionsMap := question.(map[string]interface{})
text := questionsMap["text"].(string)
helpText := questionsMap["help_text"].(string)
naEnabled := questionsMap["na_enabled"].(bool)
commentsRequired := questionsMap["comments_required"].(bool)
answerQuestions := questionsMap["answer_options"].([]interface{})
isKill := questionsMap["is_kill"].(bool)
isCritical := questionsMap["is_critical"].(bool)
sdkQuestion := platformclientv2.Evaluationquestion{
Text: &text,
HelpText: &helpText,
VarType: &questionType,
NaEnabled: &naEnabled,
CommentsRequired: &commentsRequired,
AnswerOptions: buildSdkAnswerOptions(answerQuestions),
IsKill: &isKill,
IsCritical: &isCritical,
}
visibilityCondition := questionsMap["visibility_condition"].([]interface{})
sdkQuestion.VisibilityCondition = buildSdkVisibilityCondition(visibilityCondition)
sdkQuestions = append(sdkQuestions, sdkQuestion)
}
return &sdkQuestions
}
func buildSdkAnswerOptions(answerOptions []interface{}) *[]platformclientv2.Answeroption {
sdkAnswerOptions := make([]platformclientv2.Answeroption, 0)
for _, answerOptionsList := range answerOptions {
answerOptionsMap := answerOptionsList.(map[string]interface{})
answerText := answerOptionsMap["text"].(string)
answerValue := answerOptionsMap["value"].(int)
sdkAnswerOption := platformclientv2.Answeroption{
Text: &answerText,
Value: &answerValue,
}
sdkAnswerOptions = append(sdkAnswerOptions, sdkAnswerOption)
}
return &sdkAnswerOptions
}
func buildSdkVisibilityCondition(visibilityCondition []interface{}) *platformclientv2.Visibilitycondition {
if visibilityCondition == nil || len(visibilityCondition) <= 0 {
return nil
}
visibilityConditionMap := visibilityCondition[0].(map[string]interface{})
combiningOperation := visibilityConditionMap["combining_operation"].(string)
predicates := visibilityConditionMap["predicates"].([]interface{})
return &platformclientv2.Visibilitycondition{
CombiningOperation: &combiningOperation,
Predicates: &predicates,
}
}
func flattenQuestionGroups(questionGroups *[]platformclientv2.Evaluationquestiongroup) []interface{} {
if questionGroups == nil {
return nil
}
questionGroupList := []interface{}{}
for _, questionGroup := range *questionGroups {
questionGroupMap := make(map[string]interface{})
if questionGroup.Name != nil {
questionGroupMap["name"] = *questionGroup.Name
}
if questionGroup.DefaultAnswersToHighest != nil {
questionGroupMap["default_answers_to_highest"] = *questionGroup.DefaultAnswersToHighest
}
if questionGroup.DefaultAnswersToNA != nil {
questionGroupMap["default_answers_to_na"] = *questionGroup.DefaultAnswersToNA
}
if questionGroup.NaEnabled != nil {
questionGroupMap["na_enabled"] = *questionGroup.NaEnabled
}
if questionGroup.Weight != nil {
questionGroupMap["weight"] = *questionGroup.Weight
}
if questionGroup.ManualWeight != nil {
questionGroupMap["manual_weight"] = *questionGroup.ManualWeight
}
if questionGroup.Questions != nil {
questionGroupMap["questions"] = flattenQuestions(questionGroup.Questions)
}
if questionGroup.VisibilityCondition != nil {
questionGroupMap["visibility_condition"] = flattenVisibilityCondition(questionGroup.VisibilityCondition)
}
questionGroupList = append(questionGroupList, questionGroupMap)
}
return questionGroupList
}
func flattenQuestions(questions *[]platformclientv2.Evaluationquestion) []interface{} {
if questions == nil {
return nil
}
questionList := []interface{}{}
for _, question := range *questions {
questionMap := make(map[string]interface{})
if question.Text != nil {
questionMap["text"] = *question.Text
}
if question.HelpText != nil {
questionMap["help_text"] = *question.HelpText
}
if question.NaEnabled != nil {
questionMap["na_enabled"] = *question.NaEnabled
}
if question.CommentsRequired != nil {
questionMap["comments_required"] = *question.CommentsRequired
}
if question.IsKill != nil {
questionMap["is_kill"] = *question.IsKill
}
if question.IsCritical != nil {
questionMap["is_critical"] = *question.IsCritical
}
if question.VisibilityCondition != nil {
questionMap["visibility_condition"] = flattenVisibilityCondition(question.VisibilityCondition)
}
if question.AnswerOptions != nil {
questionMap["answer_options"] = flattenAnswerOptions(question.AnswerOptions)
}
questionList = append(questionList, questionMap)
}
return questionList
}
func flattenAnswerOptions(answerOptions *[]platformclientv2.Answeroption) []interface{} {
if answerOptions == nil {
return nil
}
answerOptionsList := []interface{}{}
for _, answerOption := range *answerOptions {
answerOptionMap := make(map[string]interface{})
if answerOption.Text != nil {
answerOptionMap["text"] = *answerOption.Text
}
if answerOption.Value != nil {
answerOptionMap["value"] = *answerOption.Value
}
answerOptionsList = append(answerOptionsList, answerOptionMap)
}
return answerOptionsList
}
func flattenVisibilityCondition(visibilityCondition *platformclientv2.Visibilitycondition) []interface{} {
if visibilityCondition == nil {
return nil
}
visibilityConditionMap := make(map[string]interface{})
if visibilityCondition.CombiningOperation != nil {
visibilityConditionMap["combining_operation"] = *visibilityCondition.CombiningOperation
}
if visibilityCondition.Predicates != nil {
visibilityConditionMap["predicates"] = lists.InterfaceListToStrings(*visibilityCondition.Predicates)
}
return []interface{}{visibilityConditionMap}
}
func GenerateEvaluationFormResource(resourceID string, evaluationForm *EvaluationFormStruct) string {
return fmt.Sprintf(`resource "genesyscloud_quality_forms_evaluation" "%s" {
name = "%s"
published = %v
%s
}
`, resourceID,
evaluationForm.Name,
evaluationForm.Published,
GenerateEvaluationFormQuestionGroups(&evaluationForm.QuestionGroups),
)
}
func GenerateEvaluationFormQuestionGroups(questionGroups *[]EvaluationFormQuestionGroupStruct) string {
if questionGroups == nil {
return ""
}
questionGroupsString := ""
for _, questionGroup := range *questionGroups {
questionGroupString := fmt.Sprintf(`
question_groups {
name = "%s"
default_answers_to_highest = %v
default_answers_to_na = %v
na_enabled = %v
weight = %v
manual_weight = %v
%s
%s
}
`, questionGroup.Name,
questionGroup.DefaultAnswersToHighest,
questionGroup.DefaultAnswersToNA,
questionGroup.NaEnabled,
questionGroup.Weight,
questionGroup.ManualWeight,
GenerateEvaluationFormQuestions(&questionGroup.Questions),
GenerateFormVisibilityCondition(&questionGroup.VisibilityCondition),
)
questionGroupsString += questionGroupString
}
return questionGroupsString
}
func GenerateEvaluationFormQuestions(questions *[]EvaluationFormQuestionStruct) string {
if questions == nil {
return ""
}
questionsString := ""
for _, question := range *questions {
questionString := fmt.Sprintf(`
questions {
text = "%s"
help_text = "%s"
na_enabled = %v
comments_required = %v
is_kill = %v
is_critical = %v
%s
%s
}
`, question.Text,
question.HelpText,
question.NaEnabled,
question.CommentsRequired,
question.IsKill,
question.IsCritical,
GenerateFormVisibilityCondition(&question.VisibilityCondition),
GenerateFormAnswerOptions(&question.AnswerOptions),
)
questionsString += questionString
}
return questionsString
}
func GenerateFormAnswerOptions(answerOptions *[]AnswerOptionStruct) string {
if answerOptions == nil {
return ""
}
answerOptionsString := ""
for _, answerOption := range *answerOptions {
answerOptionString := fmt.Sprintf(`
answer_options {
text = "%s"
value = %v
}
`, answerOption.Text,
answerOption.Value,
)
answerOptionsString += answerOptionString
}
return fmt.Sprintf(`%s`, answerOptionsString)
}
func GenerateFormVisibilityCondition(condition *VisibilityConditionStruct) string {
if condition == nil || len(condition.CombiningOperation) == 0 {
return ""
}
predicateString := ""
for i, predicate := range condition.Predicates {
if i > 0 {
predicateString += ", "
}
predicateString += strconv.Quote(predicate)
}
return fmt.Sprintf(`
visibility_condition {
combining_operation = "%s"
predicates = [%s]
}
`, condition.CombiningOperation,
predicateString,
)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type SurveyFormStruct struct {
Name string
Published bool
Disabled bool
ContextId int
Language string
Header string
Footer string
QuestionGroups []SurveyFormQuestionGroupStruct
}
type SurveyFormQuestionGroupStruct struct {
Name string
NaEnabled bool
Questions []SurveyFormQuestionStruct
VisibilityCondition VisibilityConditionStruct
}
type SurveyFormQuestionStruct struct {
Text string
HelpText string
VarType string
NaEnabled bool
VisibilityCondition VisibilityConditionStruct
AnswerOptions []AnswerOptionStruct
MaxResponseCharacters int
ExplanationPrompt string
}
var (
surveyQuestionGroup = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of display question in question group.",
Type: schema.TypeString,
Required: true,
},
"na_enabled": {
Description: "Specifies whether a not applicable answer is enabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"questions": {
Description: "Questions inside the group",
Type: schema.TypeList,
Required: true,
MinItems: 1,
Elem: surveyQuestion,
},
"visibility_condition": {
Description: "Defines conditions where question would be visible",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: surveyFormVisibilityCondition,
},
},
}
surveyQuestion = &schema.Resource{
Schema: map[string]*schema.Schema{
"text": {
Description: "Individual question",
Type: schema.TypeString,
Required: true,
},
"help_text": {
Description: "Help text for the question.",
Type: schema.TypeString,
Optional: true,
},
"type": {
Description: "Valid Values: multipleChoiceQuestion, freeTextQuestion, npsQuestion, readOnlyTextBlockQuestion",
Type: schema.TypeString,
Optional: true,
Default: "multipleChoiceQuestion",
ValidateFunc: validation.StringInSlice([]string{"multipleChoiceQuestion", "freeTextQuestion", "npsQuestion", "readOnlyTextBlockQuestion"}, false),
},
"na_enabled": {
Description: "Specifies whether a not applicable answer is enabled.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"visibility_condition": {
Description: "Defines conditions where question would be visible",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: surveyFormVisibilityCondition,
},
"answer_options": {
Description: "Options from which to choose an answer for this question.",
Type: schema.TypeList,
Optional: true,
Elem: surveyFormAnswerOptions,
},
"max_response_characters": {
Description: "How many characters are allowed in the text response to this question. Used by NPS and Free Text question types.",
Type: schema.TypeInt,
Optional: true,
},
"explanation_prompt": {
Description: "Prompt for details explaining the chosen NPS score. Used by NPS questions.",
Type: schema.TypeString,
Optional: true,
},
},
}
surveyFormVisibilityCondition = &schema.Resource{
Schema: map[string]*schema.Schema{
"combining_operation": {
Description: "Valid Values: AND, OR",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"AND", "OR"}, false),
},
"predicates": {
Description: "A list of strings, each representing the location in the form of the Answer Option to depend on. In the format of \"/form/questionGroup/{questionGroupIndex}/question/{questionIndex}/answer/{answerIndex}\" or, to assume the current question group, \"../question/{questionIndex}/answer/{answerIndex}\". Note: Indexes are zero-based",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
surveyFormAnswerOptions = &schema.Resource{
Schema: map[string]*schema.Schema{
"text": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeInt,
Required: true,
},
"assistance_conditions": {
Description: "Options from which to choose an answer for this question.",
Type: schema.TypeList,
Optional: true,
Elem: assistanceConditions,
},
},
}
assistanceConditions = &schema.Resource{
Schema: map[string]*schema.Schema{
"operator": {
Description: "List of assistance conditions which are combined together with a logical AND operator. Eg ( assistanceCondtion1 && assistanceCondition2 ) wherein assistanceCondition could be ( EXISTS topic1 || topic2 || ... ) or (NOTEXISTS topic3 || topic4 || ...).",
Type: schema.TypeString,
Optional: true,
},
"topic_ids": {
Description: "List of topicIds within the assistance condition which would be combined together using logical OR operator. Eg ( topicId_1 || topicId_2 ) .",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
func getAllSurveyForms(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
qualityAPI := platformclientv2.NewQualityApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
surveyForms, resp, getErr := qualityAPI.GetQualityFormsSurveys(pageSize, pageNum, "", "", "", "", "", "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to get quality forms surveys error: %s", getErr), resp)
}
if surveyForms.Entities == nil || len(*surveyForms.Entities) == 0 {
break
}
for _, surveyForm := range *surveyForms.Entities {
resources[*surveyForm.Id] = &resourceExporter.ResourceMeta{Name: *surveyForm.Name}
}
}
return resources, nil
}
func SurveyFormExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllSurveyForms),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
AllowZeroValues: []string{"question_groups.questions.answer_options.value"},
}
}
func ResourceSurveyForm() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Survey Forms",
CreateContext: provider.CreateWithPooledClient(createSurveyForm),
ReadContext: provider.ReadWithPooledClient(readSurveyForm),
UpdateContext: provider.UpdateWithPooledClient(updateSurveyForm),
DeleteContext: provider.DeleteWithPooledClient(deleteSurveyForm),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"published": {
Description: "Specifies if the survey form is published.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"disabled": {
Description: "Is this form disabled",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"language": {
Description: "Language for survey viewer localization. Currently localized languages: da, de, en-US, es, fi, fr, it, ja, ko, nl, no, pl, pt-BR, sv, th, tr, zh-CH, zh-TW",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"da", "de", "en-US", "es", "fi", "fr", "it", "ja", "ko", "nl", "no", "pl", "pt-BR", "sv", "th", "tr", "zh-CH", "zh-TW"}, false),
},
"header": {
Description: "Markdown text for the top of the form.",
Type: schema.TypeString,
Optional: true,
},
"footer": {
Description: "Markdown text for the bottom of the form.",
Type: schema.TypeString,
Optional: true,
},
"question_groups": {
Description: "A list of question groups.",
Type: schema.TypeList,
Required: true,
MinItems: 1,
Elem: surveyQuestionGroup,
},
},
}
}
func createSurveyForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
language := d.Get("language").(string)
header := d.Get("header").(string)
footer := d.Get("footer").(string)
disabled := d.Get("disabled").(bool)
published := d.Get("published").(bool)
questionGroups, qgErr := buildSurveyQuestionGroups(d)
if qgErr != nil {
return qgErr
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
log.Printf("Creating Survey Form %s", name)
form, resp, err := qualityAPI.PostQualityFormsSurveys(platformclientv2.Surveyform{
Name: &name,
Disabled: &disabled,
Language: &language,
Header: &header,
Footer: &footer,
QuestionGroups: questionGroups,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to create survey form %s error: %s", name, err), resp)
}
// Make sure form is properly created
time.Sleep(2 * time.Second)
formId := form.Id
// Publishing
if published {
_, resp, err := qualityAPI.PostQualityPublishedformsSurveys(platformclientv2.Publishform{
Id: formId,
Published: &published,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to publish survey form %s error: %s", name, err), resp)
}
}
d.SetId(*formId)
log.Printf("Created survey form %s %s", name, *form.Id)
return readSurveyForm(ctx, d, meta)
}
func readSurveyForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceSurveyForm(), constants.DefaultConsistencyChecks, "genesyscloud_quality_forms_survey")
log.Printf("Reading survey form %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
surveyForm, resp, getErr := qualityAPI.GetQualityFormsSurvey(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to read survey form %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to read survey form %s | error: %s", d.Id(), getErr), resp))
}
if surveyForm.Name != nil {
d.Set("name", *surveyForm.Name)
}
if surveyForm.Disabled != nil {
d.Set("disabled", *surveyForm.Disabled)
}
if surveyForm.Language != nil {
d.Set("language", *surveyForm.Language)
}
if surveyForm.Header != nil {
d.Set("header", *surveyForm.Header)
}
if surveyForm.Footer != nil {
d.Set("footer", *surveyForm.Footer)
}
if surveyForm.Published != nil {
d.Set("published", *surveyForm.Published)
}
if surveyForm.QuestionGroups != nil {
d.Set("question_groups", flattenSurveyQuestionGroups(surveyForm.QuestionGroups))
}
return cc.CheckState(d)
})
}
func updateSurveyForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
language := d.Get("language").(string)
header := d.Get("header").(string)
footer := d.Get("footer").(string)
disabled := d.Get("disabled").(bool)
published := d.Get("published").(bool)
questionGroups, qgErr := buildSurveyQuestionGroups(d)
if qgErr != nil {
return qgErr
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get the latest unpublished version of the form
formVersions, getResp, err := qualityAPI.GetQualityFormsSurveyVersions(d.Id(), 25, 1)
if err != nil {
return getResp, util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to get survey form versions %s error: %s", name, err), getResp)
}
versions := *formVersions.Entities
latestUnpublishedVersion := ""
for _, v := range versions {
if !*v.Published {
latestUnpublishedVersion = *v.Id
}
}
log.Printf("Updating Survey Form %s", name)
form, putResp, err := qualityAPI.PutQualityFormsSurvey(latestUnpublishedVersion, platformclientv2.Surveyform{
Name: &name,
Disabled: &disabled,
Language: &language,
Header: &header,
Footer: &footer,
QuestionGroups: questionGroups,
})
if err != nil {
return putResp, util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to update survey form %s error: %s", name, err), putResp)
}
log.Printf("Updated survey form %s %s", name, *form.Id)
// Set published property on survey form update.
if published {
_, postResp, err := qualityAPI.PostQualityPublishedformsSurveys(platformclientv2.Publishform{
Id: form.Id,
Published: &published,
})
if err != nil {
return postResp, util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to publish survey form %s error: %s", name, err), postResp)
}
} else {
// If published property is reset to false, set the resource Id to the latest unpublished form
d.SetId(*form.Id)
}
return putResp, nil
})
if diagErr != nil {
return diagErr
}
return readSurveyForm(ctx, d, meta)
}
func deleteSurveyForm(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
qualityAPI := platformclientv2.NewQualityApiWithConfig(sdkConfig)
// Get the latest unpublished version of the form
formVersions, resp, err := qualityAPI.GetQualityFormsSurveyVersions(d.Id(), 25, 1)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to get survey form versions %s error: %s", name, err), resp)
}
versions := *formVersions.Entities
latestUnpublishedVersion := ""
for _, v := range versions {
if !*v.Published {
latestUnpublishedVersion = *v.Id
}
}
d.SetId(latestUnpublishedVersion)
log.Printf("Deleting survey form %s", name)
if resp, err := qualityAPI.DeleteQualityFormsSurvey(d.Id()); err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Failed to delete survey form %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := qualityAPI.GetQualityFormsSurvey(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// survey form deleted
log.Printf("Deleted survey form %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Error deleting survey form %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_quality_forms_survey", fmt.Sprintf("Survey form %s still exists", d.Id()), resp))
})
}
func buildSurveyQuestionGroups(d *schema.ResourceData) (*[]platformclientv2.Surveyquestiongroup, diag.Diagnostics) {
questionGroupType := "questionGroup"
var surveyQuestionGroups []platformclientv2.Surveyquestiongroup
if questionGroups, ok := d.GetOk("question_groups"); ok {
questionGroupList := questionGroups.([]interface{})
for _, questionGroup := range questionGroupList {
questionGroupsMap := questionGroup.(map[string]interface{})
questionGroupName := questionGroupsMap["name"].(string)
naEnabled := questionGroupsMap["na_enabled"].(bool)
questions := questionGroupsMap["questions"].([]interface{})
sdkquestionGroup := platformclientv2.Surveyquestiongroup{
Name: &questionGroupName,
VarType: &questionGroupType,
NaEnabled: &naEnabled,
Questions: buildSurveyQuestions(questions),
}
visibilityCondition := questionGroupsMap["visibility_condition"].([]interface{})
sdkquestionGroup.VisibilityCondition = buildSdkVisibilityCondition(visibilityCondition)
surveyQuestionGroups = append(surveyQuestionGroups, sdkquestionGroup)
}
}
return &surveyQuestionGroups, nil
}
func buildSurveyQuestions(questions []interface{}) *[]platformclientv2.Surveyquestion {
sdkQuestions := make([]platformclientv2.Surveyquestion, 0)
for _, question := range questions {
questionsMap := question.(map[string]interface{})
text := questionsMap["text"].(string)
helpText := questionsMap["help_text"].(string)
questionType := questionsMap["type"].(string)
naEnabled := questionsMap["na_enabled"].(bool)
answerQuestions := questionsMap["answer_options"].([]interface{})
maxResponseCharacters := questionsMap["max_response_characters"].(int)
sdkQuestion := platformclientv2.Surveyquestion{
Text: &text,
HelpText: &helpText,
VarType: &questionType,
NaEnabled: &naEnabled,
AnswerOptions: buildSdkAnswerOptions(answerQuestions),
MaxResponseCharacters: &maxResponseCharacters,
}
explanationPrompt := questionsMap["explanation_prompt"].(string)
if explanationPrompt != "" {
sdkQuestion.ExplanationPrompt = &explanationPrompt
}
visibilityCondition := questionsMap["visibility_condition"].([]interface{})
sdkQuestion.VisibilityCondition = buildSdkVisibilityCondition(visibilityCondition)
sdkQuestions = append(sdkQuestions, sdkQuestion)
}
return &sdkQuestions
}
func flattenSurveyQuestionGroups(questionGroups *[]platformclientv2.Surveyquestiongroup) []interface{} {
if questionGroups == nil {
return nil
}
questionGroupList := []interface{}{}
for _, questionGroup := range *questionGroups {
questionGroupMap := make(map[string]interface{})
if questionGroup.Name != nil {
questionGroupMap["name"] = *questionGroup.Name
}
if questionGroup.NaEnabled != nil {
questionGroupMap["na_enabled"] = *questionGroup.NaEnabled
}
if questionGroup.Questions != nil {
questionGroupMap["questions"] = flattenSurveyQuestions(questionGroup.Questions)
}
if questionGroup.VisibilityCondition != nil {
questionGroupMap["visibility_condition"] = flattenVisibilityCondition(questionGroup.VisibilityCondition)
}
questionGroupList = append(questionGroupList, questionGroupMap)
}
return questionGroupList
}
func flattenSurveyQuestions(questions *[]platformclientv2.Surveyquestion) []interface{} {
if questions == nil {
return nil
}
questionList := []interface{}{}
for _, question := range *questions {
questionMap := make(map[string]interface{})
if question.Text != nil {
questionMap["text"] = *question.Text
}
if question.HelpText != nil {
questionMap["help_text"] = *question.HelpText
}
if question.VarType != nil {
questionMap["type"] = *question.VarType
}
if question.NaEnabled != nil {
questionMap["na_enabled"] = *question.NaEnabled
}
if question.VisibilityCondition != nil {
questionMap["visibility_condition"] = flattenVisibilityCondition(question.VisibilityCondition)
}
if question.AnswerOptions != nil {
questionMap["answer_options"] = flattenAnswerOptions(question.AnswerOptions)
}
if question.MaxResponseCharacters != nil {
questionMap["max_response_characters"] = *question.MaxResponseCharacters
}
if question.ExplanationPrompt != nil {
questionMap["explanation_prompt"] = *question.ExplanationPrompt
}
questionList = append(questionList, questionMap)
}
return questionList
}
func GenerateSurveyFormResource(resourceID string, surveyForm *SurveyFormStruct) string {
form := fmt.Sprintf(`resource "genesyscloud_quality_forms_survey" "%s" {
name = "%s"
published = %v
disabled = %v
language = "%s"
header = "%s"
footer = "%s"
%s
%s
}
`, resourceID,
surveyForm.Name,
surveyForm.Published,
surveyForm.Disabled,
surveyForm.Language,
surveyForm.Header,
surveyForm.Footer,
generateSurveyFormQuestionGroups(&surveyForm.QuestionGroups),
generateLifeCycle(),
)
return form
}
func generateLifeCycle() string {
return `
lifecycle {
ignore_changes = [
question_groups[0].questions[0].type,
question_groups[0].questions[1].type,
question_groups[0].questions[2].type,
question_groups[1].questions[0].type,
question_groups[1].questions[1].type,
question_groups[1].questions[2].type,
question_groups[2].questions[0].type,
question_groups[2].questions[1].type,
question_groups[2].questions[2].type,
]
}
`
}
func generateSurveyFormQuestions(questions *[]SurveyFormQuestionStruct) string {
if questions == nil {
return ""
}
questionsString := ""
for _, question := range *questions {
questionString := fmt.Sprintf(`
questions {
text = "%s"
help_text = "%s"
type = "%s"
na_enabled = %v
%s
%s
max_response_characters = %v
explanation_prompt = "%s"
}
`, question.Text,
question.HelpText,
question.VarType,
question.NaEnabled,
GenerateFormVisibilityCondition(&question.VisibilityCondition),
GenerateFormAnswerOptions(&question.AnswerOptions),
question.MaxResponseCharacters,
question.ExplanationPrompt,
)
questionsString += questionString
}
return questionsString
}
func generateSurveyFormQuestionGroups(questionGroups *[]SurveyFormQuestionGroupStruct) string {
if questionGroups == nil {
return ""
}
questionGroupsString := ""
for _, questionGroup := range *questionGroups {
questionGroupString := fmt.Sprintf(`
question_groups {
name = "%s"
na_enabled = %v
%s
%s
}
`, questionGroup.Name,
questionGroup.NaEnabled,
generateSurveyFormQuestions(&questionGroup.Questions),
GenerateFormVisibilityCondition(&questionGroup.VisibilityCondition),
)
questionGroupsString += questionGroupString
}
return questionGroupsString
}
package genesyscloud
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllRoutingEmailDomains(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
domains, resp, getErr := routingAPI.GetRoutingEmailDomains(pageSize, pageNum, false, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to get routing email domains error: %s", getErr), resp)
}
if domains.Entities == nil || len(*domains.Entities) == 0 {
return resources, nil
}
for _, domain := range *domains.Entities {
resources[*domain.Id] = &resourceExporter.ResourceMeta{Name: *domain.Id}
}
}
}
func RoutingEmailDomainExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingEmailDomains),
UnResolvableAttributes: map[string]*schema.Schema{
"custom_smtp_server_id": ResourceRoutingEmailDomain().Schema["custom_smtp_server_id"],
},
}
}
func ResourceRoutingEmailDomain() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Routing Email Domain",
CreateContext: provider.CreateWithPooledClient(createRoutingEmailDomain),
ReadContext: provider.ReadWithPooledClient(readRoutingEmailDomain),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingEmailDomain),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingEmailDomain),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"domain_id": {
Description: "Unique Id of the domain such as: 'example.com'. If subdomain is true, the Genesys Cloud regional domain is appended. Changing the domain_id attribute will cause the routing_email_domain to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"subdomain": {
Description: "Indicates if this a Genesys Cloud sub-domain. If true, then the appropriate DNS records are created for sending/receiving email. Changing the subdomain attribute will cause the routing_email_domain to be dropped and recreated with a new ID.",
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"mail_from_domain": {
Description: "The custom MAIL FROM domain. This must be a subdomain of your email domain",
Type: schema.TypeString,
Optional: true,
},
"custom_smtp_server_id": {
Description: "The ID of the custom SMTP server integration to use when sending outbound emails from this domain.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
func createRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
domainID := d.Get("domain_id").(string)
subdomain := d.Get("subdomain").(bool)
mxRecordStatus := "VALID"
if !subdomain {
mxRecordStatus = "NOT_AVAILABLE"
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
sdkDomain := platformclientv2.Inbounddomain{
Id: &domainID,
SubDomain: &subdomain,
MxRecordStatus: &mxRecordStatus,
}
log.Printf("Creating routing email domain %s", domainID)
domain, resp, err := routingAPI.PostRoutingEmailDomains(sdkDomain)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to create routing email domain %s error: %s", domainID, err), resp)
}
d.SetId(*domain.Id)
log.Printf("Created routing email domain %s", *domain.Id)
// Other settings must be updated in a PATCH update
if d.HasChanges("mail_from_domain", "custom_smtp_server_id") {
return updateRoutingEmailDomain(ctx, d, meta)
} else {
return readRoutingEmailDomain(ctx, d, meta)
}
}
func readRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingEmailDomain(), constants.DefaultConsistencyChecks, "genesyscloud_routing_email_domain")
log.Printf("Reading routing email domain %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
domain, resp, getErr := routingAPI.GetRoutingEmailDomain(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to read routing email domain %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to read routing email domain %s | error: %s", d.Id(), getErr), resp))
}
if domain.SubDomain != nil && *domain.SubDomain {
// Strip off the regional domain suffix added by the server
d.Set("domain_id", strings.SplitN(*domain.Id, ".", 2)[0])
} else {
d.Set("domain_id", *domain.Id)
}
if domain.SubDomain != nil {
d.Set("subdomain", *domain.SubDomain)
} else {
d.Set("subdomain", nil)
}
if domain.CustomSMTPServer != nil && domain.CustomSMTPServer.Id != nil {
d.Set("custom_smtp_server_id", *domain.CustomSMTPServer.Id)
} else {
d.Set("custom_smtp_server_id", nil)
}
if domain.MailFromSettings != nil && domain.MailFromSettings.MailFromDomain != nil {
d.Set("mail_from_domain", *domain.MailFromSettings.MailFromDomain)
} else {
d.Set("mail_from_domain", nil)
}
log.Printf("Read routing email domain %s", d.Id())
return cc.CheckState(d)
})
}
func updateRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
customSMTPServer := d.Get("custom_smtp_server_id").(string)
mailFromDomain := d.Get("mail_from_domain").(string)
domainID := d.Get("domain_id").(string)
if !strings.Contains(mailFromDomain, domainID) || mailFromDomain == domainID {
return util.BuildDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("domain_id must be a subdomain of mail_from_domain"), fmt.Errorf("domain_id must be a subdomain of mail_from_domain"))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Updating routing email domain %s", d.Id())
_, resp, err := routingAPI.PatchRoutingEmailDomain(d.Id(), platformclientv2.Inbounddomainpatchrequest{
MailFromSettings: &platformclientv2.Mailfromresult{
MailFromDomain: &mailFromDomain,
},
CustomSMTPServer: &platformclientv2.Domainentityref{
Id: &customSMTPServer,
},
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to update routing email domain %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated routing email domain %s", d.Id())
return readRoutingEmailDomain(ctx, d, meta)
}
func deleteRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Deleting routing email domain %s", d.Id())
resp, err := routingAPI.DeleteRoutingEmailDomain(d.Id())
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to delete routing email domain %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 90*time.Second, func() *retry.RetryError {
_, resp, err := routingAPI.GetRoutingEmailDomain(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Routing email domain deleted
log.Printf("Deleted Routing email domain %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Error deleting Routing email domain %s | error: %s", d.Id(), err), resp))
}
routingAPI.DeleteRoutingEmailDomain(d.Id())
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Routing email domain %s still exists", d.Id()), resp))
})
}
func GenerateRoutingEmailDomainResource(
resourceID string,
domainID string,
subdomain string,
fromDomain string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_email_domain" "%s" {
domain_id = "%s"
subdomain = %s
mail_from_domain = %s
}
`, resourceID, domainID, subdomain, fromDomain)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllRoutingLanguages(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
languages, resp, getErr := routingAPI.GetRoutingLanguages(pageSize, pageNum, "", "", nil)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to get page of languages: %v", getErr), resp)
}
if languages.Entities == nil || len(*languages.Entities) == 0 {
break
}
for _, language := range *languages.Entities {
if language.State != nil && *language.State != "deleted" {
resources[*language.Id] = &resourceExporter.ResourceMeta{Name: *language.Name}
}
}
}
return resources, nil
}
func RoutingLanguageExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingLanguages),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceRoutingLanguage() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Routing Language",
CreateContext: provider.CreateWithPooledClient(createRoutingLanguage),
ReadContext: provider.ReadWithPooledClient(readRoutingLanguage),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingLanguage),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Language name. Changing the language_name attribute will cause the language object to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func createRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Creating language %s", name)
language, resp, err := routingAPI.PostRoutingLanguages(platformclientv2.Language{
Name: &name,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to create language %s error: %s", name, err), resp)
}
d.SetId(*language.Id)
log.Printf("Created language %s %s", name, *language.Id)
return readRoutingLanguage(ctx, d, meta)
}
func readRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingLanguage(), constants.DefaultConsistencyChecks, "genesyscloud_routing_language")
log.Printf("Reading language %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
language, resp, getErr := routingApi.GetRoutingLanguage(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to read language %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to read language %s | error: %s", d.Id(), getErr), resp))
}
if language.State != nil && *language.State == "deleted" {
d.SetId("")
return nil
}
d.Set("name", *language.Name)
log.Printf("Read language %s %s", d.Id(), *language.Name)
return cc.CheckState(d)
})
}
func deleteRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Deleting language %s", name)
resp, err := routingApi.DeleteRoutingLanguage(d.Id())
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to delete language %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
routingLanguage, resp, err := routingApi.GetRoutingLanguage(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Routing language deleted
log.Printf("Deleted Routing language %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Error deleting Routing language %s | error: %s", d.Id(), err), resp))
}
if routingLanguage.State != nil && *routingLanguage.State == "deleted" {
// Routing language deleted
log.Printf("Deleted Routing language %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Routing language %s still exists", d.Id()), resp))
})
}
func GenerateRoutingLanguageResource(
resourceID string,
name string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_language" "%s" {
name = "%s"
}
`, resourceID, name)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func ResourceRoutingSettings() *schema.Resource {
return &schema.Resource{
Description: "An organization's routing settings",
CreateContext: provider.CreateWithPooledClient(createRoutingSettings),
ReadContext: provider.ReadWithPooledClient(readRoutingSettings),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingSettings),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingSettings),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"reset_agent_on_presence_change": {
Description: "Reset agent score when agent presence changes from off-queue to on-queue",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"contactcenter": {
Description: "Contact center settings",
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"remove_skills_from_blind_transfer": {
Description: "Strip skills from transfer",
Type: schema.TypeBool,
Optional: true,
},
},
},
},
"transcription": {
Description: "Transcription settings",
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"transcription": {
Description: "Setting to enable/disable transcription capability.Valid values: Disabled, EnabledGlobally, EnabledQueueFlow",
Type: schema.TypeString,
Optional: true,
},
"transcription_confidence_threshold": {
Description: "Configure confidence threshold. The possible values are from 1 to 100",
Type: schema.TypeInt,
Optional: true,
},
"low_latency_transcription_enabled": {
Description: "Boolean flag indicating whether low latency transcription via Notification API is enabled",
Type: schema.TypeBool,
Optional: true,
},
"content_search_enabled": {
Description: "Setting to enable/disable content search",
Type: schema.TypeBool,
Optional: true,
},
},
},
},
},
}
}
func getAllRoutingSettings(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
resources["0"] = &resourceExporter.ResourceMeta{Name: "routing_settings"}
return resources, nil
}
func RoutingSettingsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingSettings),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func createRoutingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating Routing Setting")
d.SetId("settings")
return updateRoutingSettings(ctx, d, meta)
}
func readRoutingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingSettings(), constants.DefaultConsistencyChecks, "genesyscloud_routing_settings")
log.Printf("Reading setting: %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
settings, resp, getErr := routingAPI.GetRoutingSettings()
if getErr != nil {
if util.IsStatus404(resp) {
//createRoutingSettings(ctx, d, meta)
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to read Routing Setting %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to read Routing Setting %s | error: %s", d.Id(), getErr), resp))
}
if settings.ResetAgentScoreOnPresenceChange != nil {
d.Set("reset_agent_on_presence_change", *settings.ResetAgentScoreOnPresenceChange)
} else {
d.Set("reset_agent_on_presence_change", nil)
}
if diagErr := readRoutingSettingsContactCenter(d, routingAPI); diagErr != nil {
return retry.NonRetryableError(fmt.Errorf("%v", diagErr))
}
if diagErr := readRoutingSettingsTranscription(d, routingAPI); diagErr != nil {
return retry.NonRetryableError(fmt.Errorf("%v", diagErr))
}
log.Printf("Read Routing Setting")
return cc.CheckState(d)
})
}
func updateRoutingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
resetAgentOnPresenceChange := d.Get("reset_agent_on_presence_change").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Updating Routing Settings")
update := platformclientv2.Routingsettings{
ResetAgentScoreOnPresenceChange: &resetAgentOnPresenceChange,
}
diagErr := updateContactCenter(d, routingAPI)
if diagErr != nil {
return diagErr
}
diagErr = updateTranscription(d, routingAPI)
if diagErr != nil {
return diagErr
}
_, resp, err := routingAPI.PutRoutingSettings(update)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to update routing settings %s error: %s", d.Id(), err), resp)
}
time.Sleep(5 * time.Second)
log.Printf("Updated Routing Settings")
return readRoutingSettings(ctx, d, meta)
}
func deleteRoutingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Resetting Routing Setting")
resp, err := routingAPI.DeleteRoutingSettings()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to delete routing settings error: %s", err), resp)
}
log.Printf("Reset Routing Settings")
return nil
}
func readRoutingSettingsContactCenter(d *schema.ResourceData, routingAPI *platformclientv2.RoutingApi) diag.Diagnostics {
contactcenter, resp, getErr := routingAPI.GetRoutingSettingsContactcenter()
if getErr != nil {
if util.IsStatus404(resp) {
return nil
}
return util.BuildAPIDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to read contact center for routing setting %s error: %s", d.Id(), getErr), resp)
}
if contactcenter == nil {
d.Set("contactcenter", nil)
return nil
}
contactSettings := make(map[string]interface{})
if contactcenter.RemoveSkillsFromBlindTransfer != nil {
contactSettings["remove_skills_from_blind_transfer"] = *contactcenter.RemoveSkillsFromBlindTransfer
}
d.Set("contactcenter", []interface{}{contactSettings})
return nil
}
func readRoutingSettingsTranscription(d *schema.ResourceData, routingAPI *platformclientv2.RoutingApi) diag.Diagnostics {
transcription, resp, getErr := routingAPI.GetRoutingSettingsTranscription()
if getErr != nil {
if util.IsStatus404(resp) {
return nil
}
return util.BuildAPIDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to read contact center for routing settings %s error: %s", d.Id(), getErr), resp)
}
if transcription == nil {
d.Set("transcription", nil)
return nil
}
transcriptionSettings := make(map[string]interface{})
if transcription.Transcription != nil {
transcriptionSettings["transcription"] = *transcription.Transcription
}
if transcription.TranscriptionConfidenceThreshold != nil {
transcriptionSettings["transcription_confidence_threshold"] = *transcription.TranscriptionConfidenceThreshold
}
if transcription.LowLatencyTranscriptionEnabled != nil {
transcriptionSettings["low_latency_transcription_enabled"] = *transcription.LowLatencyTranscriptionEnabled
}
if transcription.ContentSearchEnabled != nil {
transcriptionSettings["content_search_enabled"] = *transcription.ContentSearchEnabled
}
d.Set("transcription", []interface{}{transcriptionSettings})
return nil
}
func updateContactCenter(d *schema.ResourceData, routingAPI *platformclientv2.RoutingApi) diag.Diagnostics {
var removeSkillsFromBlindTransfer bool
if contactCenterConfig := d.Get("contactcenter"); contactCenterConfig != nil {
if contactCenterList := contactCenterConfig.([]interface{}); len(contactCenterList) > 0 {
contactCenterMap := contactCenterList[0].(map[string]interface{})
if contactCenterMap["remove_skills_from_blind_transfer"] != nil {
removeSkillsFromBlindTransfer = contactCenterMap["remove_skills_from_blind_transfer"].(bool)
}
resp, err := routingAPI.PatchRoutingSettingsContactcenter(platformclientv2.Contactcentersettings{
RemoveSkillsFromBlindTransfer: &removeSkillsFromBlindTransfer,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to update contact center for routing settings %s error: %s", d.Id(), err), resp)
}
}
}
return nil
}
func updateTranscription(d *schema.ResourceData, routingAPI *platformclientv2.RoutingApi) diag.Diagnostics {
if transcriptionConfig := d.Get("transcription"); transcriptionConfig != nil {
if transcriptionList := transcriptionConfig.([]interface{}); len(transcriptionList) > 0 {
transcriptionMap := transcriptionList[0].(map[string]interface{})
var transcription string
var transcriptionConfidenceThreshold int
var lowLatencyTranscriptionEnabled bool
var contentSearchEnabled bool
if transcriptionMap["transcription"] != nil {
transcription = transcriptionMap["transcription"].(string)
}
if transcriptionMap["transcription_confidence_threshold"] != nil {
transcriptionConfidenceThreshold = transcriptionMap["transcription_confidence_threshold"].(int)
}
if transcriptionMap["low_latency_transcription_enabled"] != nil {
lowLatencyTranscriptionEnabled = transcriptionMap["low_latency_transcription_enabled"].(bool)
}
if transcriptionMap["content_search_enabled"] != nil {
contentSearchEnabled = transcriptionMap["content_search_enabled"].(bool)
}
_, resp, err := routingAPI.PutRoutingSettingsTranscription(platformclientv2.Transcriptionsettings{
Transcription: &transcription,
TranscriptionConfidenceThreshold: &transcriptionConfidenceThreshold,
LowLatencyTranscriptionEnabled: &lowLatencyTranscriptionEnabled,
ContentSearchEnabled: &contentSearchEnabled,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_settings", fmt.Sprintf("Failed to update Transcription for routing settings %s error: %s", d.Id(), err), resp)
}
}
}
return nil
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllRoutingSkills(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
skills, resp, getErr := routingAPI.GetRoutingSkills(pageSize, pageNum, "", nil)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Failed to get skills error: %s", getErr), resp)
}
if skills.Entities == nil || len(*skills.Entities) == 0 {
break
}
for _, skill := range *skills.Entities {
if skill.State != nil && *skill.State != "deleted" {
resources[*skill.Id] = &resourceExporter.ResourceMeta{Name: *skill.Name}
}
}
}
return resources, nil
}
func RoutingSkillExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingSkills),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceRoutingSkill() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Routing Skill",
CreateContext: provider.CreateWithPooledClient(createRoutingSkill),
ReadContext: provider.ReadWithPooledClient(readRoutingSkill),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingSkill),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Skill name. Changing the name attribute will cause the skill object object to dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func createRoutingSkill(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Creating skill %s", name)
skill, resp, err := routingAPI.PostRoutingSkills(platformclientv2.Routingskill{
Name: &name,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Failed to create skill %s error: %s", name, err), resp)
}
d.SetId(*skill.Id)
log.Printf("Created skill %s %s", name, *skill.Id)
return readRoutingSkill(ctx, d, meta)
}
func readRoutingSkill(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingSkill(), constants.DefaultConsistencyChecks, "genesyscloud_routing_skill")
log.Printf("Reading skill %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
skill, resp, getErr := routingAPI.GetRoutingSkill(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Failed to read skill %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Failed to read skill %s | error: %s", d.Id(), getErr), resp))
}
if skill.State != nil && *skill.State == "deleted" {
d.SetId("")
return nil
}
d.Set("name", *skill.Name)
log.Printf("Read skill %s %s", d.Id(), *skill.Name)
return cc.CheckState(d)
})
}
func deleteRoutingSkill(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Deleting skill %s", name)
resp, err := routingAPI.DeleteRoutingSkill(d.Id())
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Failed to delete skill %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
routingSkill, resp, err := routingAPI.GetRoutingSkill(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Routing skill deleted
log.Printf("Deleted Routing skill %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Error deleting Routing skill %s | error: %s", d.Id(), err), resp))
}
if routingSkill.State != nil && *routingSkill.State == "deleted" {
// Routing skill deleted
log.Printf("Deleted Routing skill %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Routing skill %s still exists", d.Id()), resp))
})
}
func GenerateRoutingSkillResource(
resourceID string,
name string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_skill" "%s" {
name = "%s"
}
`, resourceID, name)
}
package genesyscloud
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type SkillGroupsRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Division struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
} `json:"division,omitempty"`
SkillConditions struct{} `json:"skillConditions"` //Keep this here. Even though we do not use this field in the struct The generated attributed is used as a placeholder
}
type AllSkillGroups struct {
Entities []struct {
ID string `json:"id"`
Name string `json:"name"`
}
NextURI string `json:"nextUri"`
SelfURI string `json:"selfUri"`
PreviousURI string `json:"previousUri"`
}
func getAllSkillGroups(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig)
apiClient := &routingAPI.Configuration.APIClient
route := "/api/v2/routing/skillgroups"
headerParams := buildHeaderParams(routingAPI)
for {
path := routingAPI.Configuration.BasePath + route
skillGroupPayload := &AllSkillGroups{}
response, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil)
if err != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to get page of skill groups error: %s", err), response)
}
err = json.Unmarshal(response.RawBody, &skillGroupPayload)
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to unmarshal skill groups"), err)
}
if skillGroupPayload.Entities == nil || len(skillGroupPayload.Entities) == 0 {
break
}
for _, skillGroup := range skillGroupPayload.Entities {
resources[skillGroup.ID] = &resourceExporter.ResourceMeta{Name: skillGroup.Name}
}
if route == skillGroupPayload.NextURI || skillGroupPayload.NextURI == "" {
break
} else {
route = skillGroupPayload.NextURI
}
}
return resources, nil
}
func ResourceSkillGroupExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllSkillGroups),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
"member_division_ids": {RefType: "genesyscloud_auth_division"},
},
RemoveIfMissing: map[string][]string{
"division_id": {"division_id"},
},
JsonEncodeAttributes: []string{"skill_conditions"},
}
}
func ResourceRoutingSkillGroup() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Skill Group`,
CreateContext: provider.CreateWithPooledClient(createSkillGroups),
ReadContext: provider.ReadWithPooledClient(readSkillGroups),
UpdateContext: provider.UpdateWithPooledClient(updateSkillGroups),
DeleteContext: provider.DeleteWithPooledClient(deleteSkillGroups),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The group name",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "Description of the skill group",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"division_id": {
Description: "The division to which this entity belongs",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"skill_conditions": {
Description: "JSON encoded array of rules that will be used to determine group membership.",
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"member_division_ids": {
Description: "The IDs of member divisions to add or remove for this skill group. An empty array means all divisions will be removed, \"*\" means all divisions will be added.",
Type: schema.TypeList,
MaxItems: 50,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
func createSkillGroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return createOrUpdateSkillGroups(ctx, d, meta, "/api/v2/routing/skillgroups", true)
}
func createOrUpdateSkillGroups(ctx context.Context, d *schema.ResourceData, meta interface{}, route string, create bool) diag.Diagnostics {
if create == true {
log.Printf("Creating a skill group using")
} else {
log.Printf("Updating a skill group using")
}
// TODO: After public API endpoint is published and exposed to public, change to SDK method instead of direct invocation
skillGroupsRequest := &SkillGroupsRequest{
Name: d.Get("name").(string),
Description: d.Get("description").(string),
}
//Get the division information
divisionId := d.Get("division_id").(string)
if divisionId != "" {
skillGroupsRequest.Division.ID = divisionId
}
//Merge in skill conditions
finalSkillGroupsJson, err := mergeSkillConditionsIntoSkillGroups(d, skillGroupsRequest)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to read the before skills groups request before: %s", skillGroupsRequest.Name), err)
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
apiClient := &routingAPI.Configuration.APIClient
path := routingAPI.Configuration.BasePath + route
headerParams := buildHeaderParams(routingAPI)
/*
Since API Client expects either a struct or map of maps (of json), convert the JSON string to a map
and then pass it into API client
*/
var skillGroupsPayload map[string]interface{}
err = json.Unmarshal([]byte(finalSkillGroupsJson), &skillGroupsPayload)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to unmarshal the JSON payload while creating/updating the skills group %s", skillGroupsRequest.Name), err)
}
httpMethod := "POST"
if create == false {
httpMethod = "PATCH"
}
response, err := apiClient.CallAPI(path, httpMethod, skillGroupsPayload, headerParams, nil, nil, "", nil)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to create/update skill groups %s error: %s", skillGroupsRequest.Name, err), response)
}
//Get the results and pull out the id
skillGroupPayload := make(map[string]interface{})
err = json.Unmarshal(response.RawBody, &skillGroupPayload)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to unmarshal skill groups"), err)
}
if create == true {
id := skillGroupPayload["id"].(string)
d.SetId(id)
log.Printf("Created skill group %s %s", skillGroupsRequest.Name, id)
} else {
log.Printf("Updated skill group %s", skillGroupsRequest.Name)
}
// Update member division IDs
apiSkillGroupMemberDivisionIds, diagErr := readSkillGroupMemberDivisionIds(d, routingAPI)
if diagErr != nil {
return diagErr
}
diagErr = postSkillGroupMemberDivisions(ctx, d, meta, routingAPI, apiSkillGroupMemberDivisionIds, create)
if diagErr != nil {
return diagErr
}
return readSkillGroups(ctx, d, meta)
}
func postSkillGroupMemberDivisions(ctx context.Context, d *schema.ResourceData, meta interface{}, routingAPI *platformclientv2.RoutingApi, apiSkillGroupMemberDivisionIds []string, create bool) diag.Diagnostics {
name := d.Get("name").(string)
memberDivisionIds := d.Get("member_division_ids").([]interface{})
if memberDivisionIds == nil {
return readSkillGroups(ctx, d, meta)
}
schemaDivisionIds := lists.InterfaceListToStrings(memberDivisionIds)
toAdd, toRemove, diagErr := createListsForSkillgroupsMembersDivisionsPost(schemaDivisionIds, apiSkillGroupMemberDivisionIds, create, meta)
if diagErr != nil {
return diagErr
}
toRemove, diagErr = removeSkillGroupDivisionID(d, toRemove)
if diagErr != nil {
return diagErr
}
if len(toAdd) < 1 && len(toRemove) < 1 {
return readSkillGroups(ctx, d, meta)
}
log.Printf("Updating skill group %s member divisions", name)
skillGroupsMemberDivisionIdsPayload := make(map[string][]string, 0)
if len(toRemove) > 0 {
skillGroupsMemberDivisionIdsPayload["removeDivisionIds"] = toRemove
}
if len(toAdd) > 0 {
skillGroupsMemberDivisionIdsPayload["addDivisionIds"] = toAdd
}
headerParams := buildHeaderParams(routingAPI)
apiClient := &routingAPI.Configuration.APIClient
path := fmt.Sprintf("%s/api/v2/routing/skillgroups/%s/members/divisions", routingAPI.Configuration.BasePath, d.Id())
response, err := apiClient.CallAPI(path, "POST", skillGroupsMemberDivisionIdsPayload, headerParams, nil, nil, "", nil)
if err != nil || response.Error != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to create/update skill group %s member divisions error: %s", d.Id(), err), response)
}
log.Printf("Updated skill group %s member divisions", name)
return nil
}
func getAllAuthDivisionIds(meta interface{}) ([]string, diag.Diagnostics) {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
allIds := make([]string, 0)
divisionResourcesMap, err := getAllAuthDivisions(nil, sdkConfig)
if err != nil {
return nil, err
}
for key, _ := range divisionResourcesMap {
allIds = append(allIds, key)
}
return allIds, nil
}
func createListsForSkillgroupsMembersDivisionsPost(schemaMemberDivisionIds []string, apiMemberDivisionIds []string,
create bool, meta interface{}) ([]string, []string, diag.Diagnostics) {
toAdd := make([]string, 0)
toRemove := make([]string, 0)
if allMemberDivisionsSpecified(schemaMemberDivisionIds) {
if len(schemaMemberDivisionIds) > 1 {
return nil, nil, util.BuildDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf(`member_division_ids should not contain more than one item when the value of an item is "*"`), fmt.Errorf(`member_division_ids should not contain more than one item when the value of an item is "*"`))
}
toAdd, err := getAllAuthDivisionIds(meta)
return toAdd, nil, err
}
if len(schemaMemberDivisionIds) > 0 {
if create == true {
return schemaMemberDivisionIds, nil, nil
}
toAdd, toRemove = organizeMemberDivisionIdsForUpdate(schemaMemberDivisionIds, apiMemberDivisionIds)
return toAdd, toRemove, nil
}
// Empty array - remove all
for _, id := range apiMemberDivisionIds {
toRemove = append(toRemove, id)
}
return nil, toRemove, nil
}
/*
Sometimes you just need to get ugly. skillConditions has a recursive function that is super ugly to manage to a static Golang
Struct. So our struct always has a placeholder "skillConditions": {} field. So what I do is convert the struct to JSON and then
check to see if skill_conditions on the Terraform resource data. I then do a string replace on the skillConditions json attribute
and replace the empty stringConditions string with the contents of skill_conditions.
Not the most eloquent code, but these are uncivilized times.
*/
func mergeSkillConditionsIntoSkillGroups(d *schema.ResourceData, skillGroupsRequest *SkillGroupsRequest) (string, error) {
skillsConditionsJsonString := fmt.Sprintf(`"skillConditions": %s`, d.Get("skill_conditions").(string))
//Get the before image of the JSON. Note this a byte array
skillGroupsRequestBefore, err := json.Marshal(skillGroupsRequest)
if err != nil {
return "", err
}
skillGroupsRequestAfter := ""
//Skill conditions are present, replace skill conditions with the content of the string
if d.Get("skill_conditions").(string) != "" {
skillGroupsRequestAfter = strings.Replace(string(skillGroupsRequestBefore), `"skillConditions":{}`, skillsConditionsJsonString, 1)
} else {
//Skill conditions are not present, get rid of skill conditions.
skillGroupsRequestAfter = strings.Replace(string(skillGroupsRequestBefore), `,"skillConditions":{}`, "", 1)
}
return skillGroupsRequestAfter, nil
}
func readSkillGroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingSkillGroup(), constants.DefaultConsistencyChecks, "genesyscloud_skill_group")
// TODO: After public API endpoint is published and exposed to public, change to SDK method instead of direct invocation
apiClient := &routingAPI.Configuration.APIClient
path := routingAPI.Configuration.BasePath + "/api/v2/routing/skillgroups/" + d.Id()
// add default headers if any
headerParams := buildHeaderParams(routingAPI)
log.Printf("Reading skills group %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
skillGroupPayload := make(map[string]interface{})
response, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to retrieve skill groups %s", err), response))
}
if err == nil && response.Error != nil && response.StatusCode != http.StatusNotFound {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to retrieve skill groups. %s", err), response))
}
err = json.Unmarshal(response.RawBody, &skillGroupPayload)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("Failed to unmarshal skill groups. %s", err))
}
if err == nil && util.IsStatus404(response) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to read skill groups %s | error: %s", d.Id(), err), response))
}
name := skillGroupPayload["name"]
divisionId := skillGroupPayload["division"].(map[string]interface{})["id"]
description := skillGroupPayload["description"]
skillConditionsBytes, err := json.Marshal(skillGroupPayload["skillConditions"])
if err != nil {
return retry.NonRetryableError(fmt.Errorf("Failed to unmarshal skill groups conditions. %s", err))
}
skillConditions := string(skillConditionsBytes)
if name != "" {
d.Set("name", name)
} else {
d.Set("name", nil)
}
if divisionId != nil && divisionId != "" {
d.Set("division_id", divisionId)
} else {
d.Set("division_id", nil)
}
if description != "" {
d.Set("description", description)
} else {
d.Set("description", nil)
}
if skillConditions != "" {
d.Set("skill_conditions", skillConditions)
} else {
d.Set("skill_conditions", nil)
}
apiMemberDivisionIds, diagErr := readSkillGroupMemberDivisionIds(d, routingAPI)
if diagErr != nil {
return retry.NonRetryableError(fmt.Errorf("%v", diagErr))
}
var schemaMemberDivisionIds []string
if divIds, ok := d.Get("member_division_ids").([]interface{}); ok {
schemaMemberDivisionIds = lists.InterfaceListToStrings(divIds)
}
memberDivisionIds := organizeMemberDivisionIdsForRead(schemaMemberDivisionIds, apiMemberDivisionIds, divisionId.(string))
_ = d.Set("member_division_ids", memberDivisionIds)
log.Printf("Read skill groups name %s %s", d.Id(), name)
return cc.CheckState(d)
})
}
func buildHeaderParams(routingAPI *platformclientv2.RoutingApi) map[string]string {
headerParams := make(map[string]string)
for key := range routingAPI.Configuration.DefaultHeader {
headerParams[key] = routingAPI.Configuration.DefaultHeader[key]
}
headerParams["Authorization"] = "Bearer " + routingAPI.Configuration.AccessToken
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
return headerParams
}
func updateSkillGroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
route := "/api/v2/routing/skillgroups/" + d.Id()
return createOrUpdateSkillGroups(ctx, d, meta, route, false)
}
func deleteSkillGroups(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
// TODO: After public API endpoint is published and exposed to public, change to SDK method instead of direct invocation
apiClient := &routingAPI.Configuration.APIClient
path := routingAPI.Configuration.BasePath + "/api/v2/routing/skillgroups/" + d.Id()
// add default headers if any
headerParams := buildHeaderParams(routingAPI)
log.Printf("Deleting skills group %s", name)
response, err := apiClient.CallAPI(path, "DELETE", nil, headerParams, nil, nil, "", nil)
if err != nil {
if util.IsStatus404(response) {
//Skills Group already deleted
log.Printf("Skills Group was already deleted %s", d.Id())
return nil
}
return util.BuildAPIDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to delete skill group %s error: %s", d.Id(), err), response)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
log.Printf("Deleting skills group %s", name)
response, err := apiClient.CallAPI(path, "DELETE", nil, headerParams, nil, nil, "", nil)
if err != nil {
if util.IsStatus404(response) {
// Skills Group Deleted
log.Printf("Deleted skills group %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Error deleting skill group %s | error: %s", d.Id(), err), response))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Skill group %s still exists", d.Id()), response))
})
}
func readSkillGroupMemberDivisionIds(d *schema.ResourceData, routingAPI *platformclientv2.RoutingApi) ([]string, diag.Diagnostics) {
headers := buildHeaderParams(routingAPI)
apiClient := &routingAPI.Configuration.APIClient
path := fmt.Sprintf("%s/api/v2/routing/skillgroups/%s/members/divisions", routingAPI.Configuration.BasePath, d.Id())
log.Printf("Reading skill group %s member divisions", d.Get("name").(string))
response, err := apiClient.CallAPI(path, "GET", nil, headers, nil, nil, "", nil)
if err != nil || response.Error != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to get member divisions for skill group %s error: %s", d.Id(), err), response)
}
memberDivisionsPayload := make(map[string]interface{}, 0)
err = json.Unmarshal(response.RawBody, &memberDivisionsPayload)
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_routing_skill_group", fmt.Sprintf("Failed to unmarshal member divisions"), err)
}
apiSkillGroupMemberDivisionIds := make([]string, 0)
entities := memberDivisionsPayload["entities"].([]interface{})
for _, entity := range entities {
if entityMap, ok := entity.(map[string]interface{}); ok {
apiSkillGroupMemberDivisionIds = append(apiSkillGroupMemberDivisionIds, entityMap["id"].(string))
}
}
log.Printf("Read skill group %s member divisions", d.Get("name").(string))
return apiSkillGroupMemberDivisionIds, nil
}
func allMemberDivisionsSpecified(schemaSkillGroupMemberDivisionIds []string) bool {
return lists.ItemInSlice("*", schemaSkillGroupMemberDivisionIds)
}
func organizeMemberDivisionIdsForUpdate(schemaIds, apiIds []string) ([]string, []string) {
toAdd := make([]string, 0)
toRemove := make([]string, 0)
// items that are in hcl and not in api-returned list - add
for _, id := range schemaIds {
if !lists.ItemInSlice(id, apiIds) {
toAdd = append(toAdd, id)
}
}
// items that are not in hcl and are in api-returned list - remove
for _, id := range apiIds {
if !lists.ItemInSlice(id, schemaIds) {
toRemove = append(toRemove, id)
}
}
return toAdd, toRemove
}
// Prepare member_division_ids list to avoid an unnecessary plan not empty error
func organizeMemberDivisionIdsForRead(schemaList, apiList []string, divisionId string) []string {
if !lists.ItemInSlice(divisionId, schemaList) {
apiList = lists.RemoveStringFromSlice(divisionId, apiList)
}
if len(schemaList) == 1 && schemaList[0] == "*" {
return schemaList
} else {
// if hcl & api lists are the same but with different ordering - set with original ordering
if lists.AreEquivalent(schemaList, apiList) {
return schemaList
} else {
return apiList
}
}
}
// Remove the value of division_id, or if this field was left blank; the home division ID
func removeSkillGroupDivisionID(d *schema.ResourceData, list []string) ([]string, diag.Diagnostics) {
if len(list) == 0 || list == nil {
return list, nil
}
divisionId := d.Get("division_id").(string)
if divisionId == "" {
id, diagErr := util.GetHomeDivisionID()
if diagErr != nil {
return nil, diagErr
}
divisionId = id
}
if lists.ItemInSlice(divisionId, list) {
list = lists.RemoveStringFromSlice(divisionId, list)
}
return list, nil
}
func GenerateRoutingSkillGroupResourceBasic(
resourceID string,
name string,
description string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_skill_group" "%s" {
name = "%s"
description="%s"
}
`, resourceID, name, description)
}
package genesyscloud
import (
"context"
"encoding/json"
"fmt"
"log"
"sort"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type MediaUtilization struct {
MaximumCapacity int32 `json:"maximumCapacity"`
InterruptableMediaTypes []string `json:"interruptableMediaTypes"`
IncludeNonAcd bool `json:"includeNonAcd"`
}
type LabelUtilization struct {
MaximumCapacity int32 `json:"maximumCapacity"`
InterruptingLabelIds []string `json:"interruptingLabelIds"`
}
type OrgUtilizationWithLabels struct {
Utilization map[string]MediaUtilization `json:"utilization"`
LabelUtilizations map[string]LabelUtilization `json:"labelUtilizations"`
}
var (
// Map of SDK media type name to schema media type name
utilizationMediaTypes = map[string]string{
"call": "call",
"callback": "callback",
"chat": "chat",
"email": "email",
"message": "message",
}
utilizationSettingsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"maximum_capacity": {
Description: "Maximum capacity of conversations of this media type. Value must be between 0 and 25.",
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(0, 25),
},
"interruptible_media_types": {
Description: fmt.Sprintf("Set of other media types that can interrupt this media type (%s).", strings.Join(getSdkUtilizationTypes(), " | ")),
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"include_non_acd": {
Description: "Block this media type when on a non-ACD conversation.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
utilizationLabelResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"label_id": {
Description: "Id of the label being configured.",
Type: schema.TypeString,
Required: true,
},
"maximum_capacity": {
Description: "Maximum capacity of conversations with this label. Value must be between 0 and 25.",
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(0, 25),
},
"interrupting_label_ids": {
Description: "Set of other labels that can interrupt this label.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
func getSdkUtilizationTypes() []string {
types := make([]string, 0, len(utilizationMediaTypes))
for t := range utilizationMediaTypes {
types = append(types, t)
}
sort.Strings(types)
return types
}
func getAllRoutingUtilization(_ context.Context, _ *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
// Routing utilization config always exists
resources := make(resourceExporter.ResourceIDMetaMap)
resources["0"] = &resourceExporter.ResourceMeta{Name: "routing_utilization"}
return resources, nil
}
func RoutingUtilizationExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingUtilization),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
AllowZeroValues: []string{"maximum_capacity"},
}
}
func ResourceRoutingUtilization() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Org-wide Routing Utilization Settings.",
CreateContext: provider.CreateWithPooledClient(createRoutingUtilization),
ReadContext: provider.ReadWithPooledClient(readRoutingUtilization),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingUtilization),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingUtilization),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Timeouts: &schema.ResourceTimeout{
Update: schema.DefaultTimeout(8 * time.Minute),
Read: schema.DefaultTimeout(8 * time.Minute),
},
Schema: map[string]*schema.Schema{
"call": {
Description: "Call media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: utilizationSettingsResource,
},
"callback": {
Description: "Callback media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: utilizationSettingsResource,
},
"message": {
Description: "Message media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: utilizationSettingsResource,
},
"email": {
Description: "Email media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: utilizationSettingsResource,
},
"chat": {
Description: "Chat media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: utilizationSettingsResource,
},
"label_utilizations": {
Description: "Label utilization settings. If not set, default label settings will be applied. This is in PREVIEW and should not be used unless the feature is available to your organization.",
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: utilizationLabelResource,
},
},
}
}
func createRoutingUtilization(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
log.Printf("Creating Routing Utilization")
d.SetId("routing_utilization")
return updateRoutingUtilization(ctx, d, meta)
}
func readRoutingUtilization(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Calling the Utilization API directly while the label feature is not available.
// Once it is, this code can go back to using platformclientv2's RoutingApi to make the call.
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
apiClient := &routingAPI.Configuration.APIClient
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingSkill(), constants.DefaultConsistencyChecks, "genesyscloud_routing_utilization")
path := fmt.Sprintf("%s/api/v2/routing/utilization", routingAPI.Configuration.BasePath)
headerParams := buildHeaderParams(routingAPI)
log.Printf("Reading Routing Utilization")
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
response, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil)
if err != nil {
if util.IsStatus404(response) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization", fmt.Sprintf("Failed to read Routing Utilization: %s", err), response))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization", fmt.Sprintf("Failed to read Routing Utilization: %s", err), response))
}
orgUtilization := &OrgUtilizationWithLabels{}
err = json.Unmarshal(response.RawBody, &orgUtilization)
if orgUtilization.Utilization != nil {
for sdkType, schemaType := range utilizationMediaTypes {
if mediaSettings, ok := orgUtilization.Utilization[sdkType]; ok {
d.Set(schemaType, flattenUtilizationSetting(mediaSettings))
} else {
d.Set(schemaType, nil)
}
}
}
if orgUtilization.LabelUtilizations != nil {
originalLabelUtilizations := d.Get("label_utilizations").([]interface{})
// Only add to the state the configured labels, in the configured order, but not any extras, to help terraform with matching new and old state.
flattenedLabelUtilizations := filterAndFlattenLabelUtilizations(orgUtilization.LabelUtilizations, originalLabelUtilizations)
d.Set("label_utilizations", flattenedLabelUtilizations)
}
log.Printf("Read Routing Utilization")
return cc.CheckState(d)
})
}
func updateRoutingUtilization(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
var resp *platformclientv2.APIResponse
var err error
log.Printf("Updating Routing Utilization")
labelUtilizations := d.Get("label_utilizations").([]interface{})
// Retrying on 409s because if a label is created immediately before the utilization update, it can lead to a conflict while the utilization is being updated to handle the new label.
diagErr := util.RetryWhen(util.IsStatus409, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// If the resource has label(s), calls the Utilization API directly.
// This code can go back to using platformclientv2's RoutingApi to make the call once label utilization is available in platformclientv2's RoutingApi.
if labelUtilizations != nil && len(labelUtilizations) > 0 {
apiClient := &routingAPI.Configuration.APIClient
path := fmt.Sprintf("%s/api/v2/routing/utilization", routingAPI.Configuration.BasePath)
headerParams := buildHeaderParams(routingAPI)
requestPayload := make(map[string]interface{})
requestPayload["utilization"] = buildSdkMediaUtilizations(d)
requestPayload["labelUtilizations"] = buildLabelUtilizationsRequest(labelUtilizations)
resp, err = apiClient.CallAPI(path, "PUT", requestPayload, headerParams, nil, nil, "", nil)
} else {
_, resp, err = routingAPI.PutRoutingUtilization(platformclientv2.Utilizationrequest{
Utilization: buildSdkMediaUtilizations(d),
})
}
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_routing_utilization", fmt.Sprintf("Failed to update Routing Utilization %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated Routing Utilization")
return readRoutingUtilization(ctx, d, meta)
}
func deleteRoutingUtilization(_ context.Context, _ *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
// Resets to default values
log.Printf("Resetting Routing Utilization")
resp, err := routingAPI.DeleteRoutingUtilization()
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_utilization", fmt.Sprintf("Failed to reset Routing Utilization error: %s", err), resp)
}
log.Printf("Reset Routing Utilization")
return nil
}
func flattenUtilizationSetting(settings MediaUtilization) []interface{} {
settingsMap := make(map[string]interface{})
settingsMap["maximum_capacity"] = settings.MaximumCapacity
settingsMap["include_non_acd"] = settings.IncludeNonAcd
if settings.InterruptableMediaTypes != nil {
settingsMap["interruptible_media_types"] = lists.StringListToSet(settings.InterruptableMediaTypes)
}
return []interface{}{settingsMap}
}
func filterAndFlattenLabelUtilizations(labelUtilizations map[string]LabelUtilization, originalLabelUtilizations []interface{}) []interface{} {
flattenedLabelUtilizations := make([]interface{}, 0)
for _, originalLabelUtilization := range originalLabelUtilizations {
originalLabelId := (originalLabelUtilization.(map[string]interface{}))["label_id"].(string)
for currentLabelId, currentLabelUtilization := range labelUtilizations {
if currentLabelId == originalLabelId {
flattenedLabelUtilizations = append(flattenedLabelUtilizations, flattenLabelUtilization(currentLabelId, currentLabelUtilization))
delete(labelUtilizations, currentLabelId)
break
}
}
}
return flattenedLabelUtilizations
}
func flattenLabelUtilization(labelId string, labelUtilization LabelUtilization) map[string]interface{} {
utilizationMap := make(map[string]interface{})
utilizationMap["label_id"] = labelId
utilizationMap["maximum_capacity"] = labelUtilization.MaximumCapacity
if labelUtilization.InterruptingLabelIds != nil {
utilizationMap["interrupting_label_ids"] = lists.StringListToSet(labelUtilization.InterruptingLabelIds)
}
return utilizationMap
}
func buildSdkMediaUtilizations(d *schema.ResourceData) *map[string]platformclientv2.Mediautilization {
settings := make(map[string]platformclientv2.Mediautilization)
for sdkType, schemaType := range utilizationMediaTypes {
mediaSettings := d.Get(schemaType).([]interface{})
if mediaSettings != nil && len(mediaSettings) > 0 {
settings[sdkType] = buildSdkMediaUtilization(mediaSettings)
}
}
return &settings
}
func buildSdkMediaUtilization(settings []interface{}) platformclientv2.Mediautilization {
settingsMap := settings[0].(map[string]interface{})
maxCapacity := settingsMap["maximum_capacity"].(int)
includeNonAcd := settingsMap["include_non_acd"].(bool)
// Optional
interruptableMediaTypes := &[]string{}
if types, ok := settingsMap["interruptible_media_types"]; ok {
interruptableMediaTypes = lists.SetToStringList(types.(*schema.Set))
}
return platformclientv2.Mediautilization{
MaximumCapacity: &maxCapacity,
IncludeNonAcd: &includeNonAcd,
InterruptableMediaTypes: interruptableMediaTypes,
}
}
func buildLabelUtilizationsRequest(labelUtilizations []interface{}) map[string]LabelUtilization {
request := make(map[string]LabelUtilization)
for _, labelUtilization := range labelUtilizations {
labelUtilizationMap := labelUtilization.(map[string]interface{})
interruptingLabelIds := lists.SetToStringList(labelUtilizationMap["interrupting_label_ids"].(*schema.Set))
request[labelUtilizationMap["label_id"].(string)] = LabelUtilization{
MaximumCapacity: int32(labelUtilizationMap["maximum_capacity"].(int)),
InterruptingLabelIds: *interruptingLabelIds,
}
}
return request
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllRoutingUtilizationLabels(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
labels, resp, getErr := routingAPI.GetRoutingUtilizationLabels(pageSize, pageNum, "", "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Failed to get page of labels error: %s", getErr), resp)
}
if labels.Entities == nil || len(*labels.Entities) == 0 {
break
}
for _, label := range *labels.Entities {
resources[*label.Id] = &resourceExporter.ResourceMeta{Name: *label.Name}
}
}
return resources, nil
}
func RoutingUtilizationLabelExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingUtilizationLabels),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceRoutingUtilizationLabel() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Routing Utilization Label. This resource is not yet widely available. Only use it if the feature is enabled.",
CreateContext: provider.CreateWithPooledClient(createRoutingUtilizationLabel),
ReadContext: provider.ReadWithPooledClient(readRoutingUtilizationLabel),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingUtilizationLabel),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingUtilizationLabel),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Label name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func createRoutingUtilizationLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Creating label %s", name)
label, resp, err := routingAPI.PostRoutingUtilizationLabels(platformclientv2.Createutilizationlabelrequest{
Name: &name,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Failed to create label %s error: %s", name, err), resp)
}
d.SetId(*label.Id)
log.Printf("Created label %s %s", name, *label.Id)
return readRoutingUtilizationLabel(ctx, d, meta)
}
func updateRoutingUtilizationLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
id := d.Id()
name := d.Get("name").(string)
log.Printf("Updating label %s with name %s", id, name)
_, resp, err := routingAPI.PutRoutingUtilizationLabel(id, platformclientv2.Updateutilizationlabelrequest{
Name: &name,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Failed to update label %s error: %s", id, err), resp)
}
log.Printf("Updated label %s", id)
return readRoutingUtilizationLabel(ctx, d, meta)
}
func readRoutingUtilizationLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingUtilizationLabel(), constants.DefaultConsistencyChecks, "genesyscloud_routing_utilization_label")
log.Printf("Reading label %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
label, resp, getErr := routingApi.GetRoutingUtilizationLabel(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Failed to read label %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Failed to read label %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *label.Name)
log.Printf("Read label %s %s", d.Id(), *label.Name)
return cc.CheckState(d)
})
}
func deleteRoutingUtilizationLabel(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Deleting label %s", name)
resp, err := routingApi.DeleteRoutingUtilizationLabel(d.Id(), true)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Failed to delete label %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := routingApi.GetRoutingUtilizationLabel(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Routing label deleted
log.Printf("Deleted Routing label %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Error deleting Routing label %s: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_utilization_label", fmt.Sprintf("Routing label %s still exists", d.Id()), resp))
})
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllRoutingWrapupCodes(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
wrapupcodes, resp, getErr := routingAPI.GetRoutingWrapupcodes(pageSize, pageNum, "", "", "", []string{}, []string{})
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Failed to get wrapupcodes error: %s", getErr), resp)
}
if wrapupcodes.Entities == nil || len(*wrapupcodes.Entities) == 0 {
break
}
for _, wrapupcode := range *wrapupcodes.Entities {
resources[*wrapupcode.Id] = &resourceExporter.ResourceMeta{Name: *wrapupcode.Name}
}
}
return resources, nil
}
func RoutingWrapupCodeExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingWrapupCodes),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func ResourceRoutingWrapupCode() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Routing Wrapup Code",
CreateContext: provider.CreateWithPooledClient(createRoutingWrapupCode),
ReadContext: provider.ReadWithPooledClient(readRoutingWrapupCode),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingWrapupCode),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingWrapupCode),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Wrapup Code name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func createRoutingWrapupCode(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Creating wrapupcode %s", name)
wrapupcode, resp, err := routingAPI.PostRoutingWrapupcodes(platformclientv2.Wrapupcoderequest{
Name: &name,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Failed to create wrapupcode %s error: %s", name, err), resp)
}
d.SetId(*wrapupcode.Id)
log.Printf("Created wrapupcode %s %s", name, *wrapupcode.Id)
return readRoutingWrapupCode(ctx, d, meta)
}
func readRoutingWrapupCode(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingWrapupCode(), constants.DefaultConsistencyChecks, "genesyscloud_routing_wrapupcode")
log.Printf("Reading wrapupcode %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
wrapupcode, resp, getErr := routingAPI.GetRoutingWrapupcode(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Failed to read wrapupcode %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Failed to read wrapupcode %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *wrapupcode.Name)
log.Printf("Read wrapupcode %s %s", d.Id(), *wrapupcode.Name)
return cc.CheckState(d)
})
}
func updateRoutingWrapupCode(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Updating wrapupcode %s", name)
_, resp, err := routingAPI.PutRoutingWrapupcode(d.Id(), platformclientv2.Wrapupcoderequest{
Name: &name,
})
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Failed to update wrapupcode %s error: %s", name, err), resp)
}
log.Printf("Updated wrapupcode %s", name)
return readRoutingWrapupCode(ctx, d, meta)
}
func deleteRoutingWrapupCode(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Deleting wrapupcode %s", name)
resp, err := routingAPI.DeleteRoutingWrapupcode(d.Id())
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Failed to delete wrapupcode %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := routingAPI.GetRoutingWrapupcode(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Routing wrapup code deleted
log.Printf("Deleted Routing wrapup code %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Error deleting Routing wrapup code %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_wrapupcode", fmt.Sprintf("Routing wrapup code %s still exists", d.Id()), resp))
})
}
func GenerateRoutingWrapupcodeResource(
resourceID string,
name string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_wrapupcode" "%s" {
name = "%s"
}
`, resourceID, name)
}
package genesyscloud
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/validators"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
chunksProcess "terraform-provider-genesyscloud/genesyscloud/util/chunks"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"github.com/nyaruka/phonenumbers"
)
type AgentUtilizationWithLabels struct {
Utilization map[string]MediaUtilization `json:"utilization"`
LabelUtilizations map[string]LabelUtilization `json:"labelUtilizations"`
Level string `json:"level"`
}
var (
contactTypeEmail = "EMAIL"
phoneNumberResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"number": {
Description: "Phone number. Phone number must be in an E.164 number format.",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validators.ValidatePhoneNumber,
},
"media_type": {
Description: "Media type of phone number (SMS | PHONE).",
Type: schema.TypeString,
Optional: true,
Default: "PHONE",
ValidateFunc: validation.StringInSlice([]string{"PHONE", "SMS"}, false),
},
"type": {
Description: "Type of number (WORK | WORK2 | WORK3 | WORK4 | HOME | MOBILE | OTHER).",
Type: schema.TypeString,
Optional: true,
Default: "WORK",
ValidateFunc: validation.StringInSlice([]string{"WORK", "WORK2", "WORK3", "WORK4", "HOME", "MOBILE", "OTHER"}, false),
},
"extension": {
Description: "Phone number extension",
Type: schema.TypeString,
Optional: true,
},
},
}
otherEmailResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"address": {
Description: "Email address.",
Type: schema.TypeString,
Required: true,
},
"type": {
Description: "Type of email address (WORK | HOME).",
Type: schema.TypeString,
Optional: true,
Default: "WORK",
ValidateFunc: validation.StringInSlice([]string{"WORK", "HOME"}, false),
},
},
}
userSkillResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"skill_id": {
Description: "ID of routing skill.",
Type: schema.TypeString,
Required: true,
},
"proficiency": {
Description: "Rating from 0.0 to 5.0 on how competent an agent is for a particular skill. It is used when a queue is set to 'Best available skills' mode to allow acd interactions to target agents with higher proficiency ratings.",
Type: schema.TypeFloat,
Required: true,
ValidateFunc: validation.FloatBetween(0, 5),
},
},
}
userLanguageResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"language_id": {
Description: "ID of routing language.",
Type: schema.TypeString,
Required: true,
},
"proficiency": {
Description: "Proficiency is a rating from 0 to 5 on how competent an agent is for a particular language. It is used when a queue is set to 'Best available language' mode to allow acd interactions to target agents with higher proficiency ratings.",
Type: schema.TypeInt, // The API accepts a float, but the backend rounds to the nearest int
Required: true,
ValidateFunc: validation.IntBetween(0, 5),
},
},
}
userLocationResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"location_id": {
Description: "ID of location.",
Type: schema.TypeString,
Required: true,
},
"notes": {
Description: "Optional description on the user's location.",
Type: schema.TypeString,
Optional: true,
},
},
}
)
func GetAllUsers(ctx context.Context, sdkConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig)
// Newly created resources often aren't returned unless there's a delay
time.Sleep(5 * time.Second)
// Inner function to get user based on status
getUsersByStatus := func(userStatus string) (*[]platformclientv2.User, error) {
users := []platformclientv2.User{}
const pageSize = 100
usersList, _, err := usersAPI.GetUsers(pageSize, 1, nil, nil, "", nil, "", userStatus)
if err != nil {
return nil, err
}
users = append(users, *usersList.Entities...)
for pageNum := 2; pageNum <= *usersList.PageCount; pageNum++ {
usersList, _, err := usersAPI.GetUsers(pageSize, pageNum, nil, nil, "", nil, "", userStatus)
if err != nil {
return nil, err
}
users = append(users, *usersList.Entities...)
}
return &users, nil
}
// Get all "active" and "inactive" users
allUsers := []platformclientv2.User{}
activeUsers, err := getUsersByStatus("active")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_user", fmt.Sprintf("failed to get 'active' users"), err)
}
allUsers = append(allUsers, *activeUsers...)
inactiveUsers, err := getUsersByStatus("inactive")
if err != nil {
return nil, util.BuildDiagnosticError("genesyscloud_user", fmt.Sprintf("failed to get 'inactive' users"), err)
}
allUsers = append(allUsers, *inactiveUsers...)
// Add resources to metamap
for _, user := range allUsers {
resources[*user.Id] = &resourceExporter.ResourceMeta{Name: *user.Email}
}
return resources, nil
}
func UserExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(GetAllUsers),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"manager": {RefType: "genesyscloud_user"},
"division_id": {RefType: "genesyscloud_auth_division"},
"routing_skills.skill_id": {RefType: "genesyscloud_routing_skill"},
"routing_languages.language_id": {RefType: "genesyscloud_routing_language"},
"locations.location_id": {RefType: "genesyscloud_location"},
},
RemoveIfMissing: map[string][]string{
"routing_skills": {"skill_id"},
"routing_languages": {"language_id"},
"locations": {"location_id"},
},
AllowZeroValues: []string{"routing_skills.proficiency", "routing_languages.proficiency"},
}
}
func ResourceUser() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud User",
CreateContext: provider.CreateWithPooledClient(createUser),
ReadContext: provider.ReadWithPooledClient(readUser),
UpdateContext: provider.UpdateWithPooledClient(updateUser),
DeleteContext: provider.DeleteWithPooledClient(deleteUser),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"email": {
Description: "User's primary email and username.",
Type: schema.TypeString,
Required: true,
},
"name": {
Description: "User's full name.",
Type: schema.TypeString,
Required: true,
},
"password": {
Description: "User's password. If specified, this is only set on user create.",
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
"state": {
Description: "User's state (active | inactive). Default is 'active'.",
Type: schema.TypeString,
Optional: true,
Default: "active",
ValidateFunc: validation.StringInSlice([]string{"active", "inactive"}, false),
},
"division_id": {
Description: "The division to which this user will belong. If not set, the home division will be used.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"department": {
Description: "User's department.",
Type: schema.TypeString,
Optional: true,
},
"title": {
Description: "User's title.",
Type: schema.TypeString,
Optional: true,
},
"manager": {
Description: "User ID of this user's manager.",
Type: schema.TypeString,
Optional: true,
},
"acd_auto_answer": {
Description: "Enable ACD auto-answer.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"routing_skills": {
Description: "Skills and proficiencies for this user. If not set, this resource will not manage user skills.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: userSkillResource,
},
"routing_languages": {
Description: "Languages and proficiencies for this user. If not set, this resource will not manage user languages.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: userLanguageResource,
},
"locations": {
Description: "The user placement at each site location. If not set, this resource will not manage user locations.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: userLocationResource,
},
"addresses": {
Description: "The address settings for this user. If not set, this resource will not manage addresses.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"other_emails": {
Description: "Other Email addresses for this user.",
Type: schema.TypeSet,
Optional: true,
Elem: otherEmailResource,
ConfigMode: schema.SchemaConfigModeAttr,
},
"phone_numbers": {
Description: "Phone number addresses for this user.",
Type: schema.TypeSet,
Optional: true,
Set: phoneNumberHash,
Elem: phoneNumberResource,
ConfigMode: schema.SchemaConfigModeAttr,
},
},
},
},
"profile_skills": {
Description: "Profile skills for this user. If not set, this resource will not manage profile skills.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"certifications": {
Description: "Certifications for this user. If not set, this resource will not manage certifications.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"employer_info": {
Description: "The employer info for this user. If not set, this resource will not manage employer info.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"official_name": {
Description: "User's official name.",
Type: schema.TypeString,
Optional: true,
},
"employee_id": {
Description: "Employee ID.",
Type: schema.TypeString,
Optional: true,
},
"employee_type": {
Description: "Employee type (Full-time | Part-time | Contractor).",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Full-time", "Part-time", "Contractor"}, false),
},
"date_hire": {
Description: "Hiring date. Dates must be an ISO-8601 string. For example: yyyy-MM-dd.",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validators.ValidateDate,
},
},
},
},
"routing_utilization": {
Description: "The routing utilization settings for this user. If empty list, the org default settings are used. If not set, this resource will not manage the users's utilization settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"call": {
Description: "Call media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: utilizationSettingsResource,
},
"callback": {
Description: "Callback media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: utilizationSettingsResource,
},
"message": {
Description: "Message media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: utilizationSettingsResource,
},
"email": {
Description: "Email media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: utilizationSettingsResource,
},
"chat": {
Description: "Chat media settings. If not set, this reverts to the default media type settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: utilizationSettingsResource,
},
"label_utilizations": {
Description: "Label utilization settings. If not set, default label settings will be applied. This is in PREVIEW and should not be used unless the feature is available to your organization.",
Type: schema.TypeList,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: utilizationLabelResource,
},
},
},
},
},
}
}
func createUser(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
email := d.Get("email").(string)
name := d.Get("name").(string)
password := d.Get("password").(string)
state := d.Get("state").(string)
divisionID := d.Get("division_id").(string)
department := d.Get("department").(string)
title := d.Get("title").(string)
manager := d.Get("manager").(string)
acdAutoAnswer := d.Get("acd_auto_answer").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig)
addresses, addrErr := buildSdkAddresses(d)
if addrErr != nil {
return addrErr
}
// Check for a deleted user before creating
id, _ := getDeletedUserId(email, usersAPI)
if id != nil {
d.SetId(*id)
return restoreDeletedUser(ctx, d, meta, usersAPI)
}
createUser := platformclientv2.Createuser{
Email: &email,
Name: &name,
State: &state,
Addresses: addresses,
Department: &department,
Title: &title,
}
// Optional attributes that should not be empty strings
if password != "" {
createUser.Password = &password
}
if divisionID != "" {
createUser.DivisionId = &divisionID
}
log.Printf("Creating user %s", email)
user, resp, err := usersAPI.PostUsers(createUser)
if err != nil {
if resp != nil && resp.Error != nil && (*resp.Error).Code == "general.conflict" {
// Check for a deleted user
id, diagErr := getDeletedUserId(email, usersAPI)
if diagErr != nil {
return diagErr
}
if id != nil {
d.SetId(*id)
return restoreDeletedUser(ctx, d, meta, usersAPI)
}
}
return util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to create user %s error: %s", email, err), resp)
}
d.SetId(*user.Id)
// Set attributes that can only be modified in a patch
if d.HasChanges(
"manager",
"locations",
"acd_auto_answer",
"profile_skills",
"certifications",
"employer_info") {
log.Printf("Updating additional attributes for user %s", email)
_, resp, patchErr := usersAPI.PatchUser(d.Id(), platformclientv2.Updateuser{
Manager: &manager,
Locations: buildSdkLocations(d),
AcdAutoAnswer: &acdAutoAnswer,
Certifications: buildSdkCertifications(d),
EmployerInfo: buildSdkEmployerInfo(d),
Version: user.Version,
})
if patchErr != nil {
return util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to update user %s error: %s", d.Id(), err), resp)
}
}
diagErr := updateUserSkills(d, usersAPI)
if diagErr != nil {
return diagErr
}
diagErr = updateUserLanguages(d, usersAPI)
if diagErr != nil {
return diagErr
}
diagErr = updateUserProfileSkills(d, usersAPI)
if diagErr != nil {
return diagErr
}
diagErr = updateUserRoutingUtilization(d, usersAPI, sdkConfig)
if diagErr != nil {
return diagErr
}
log.Printf("Created user %s %s", email, *user.Id)
return readUser(ctx, d, meta)
}
func readUser(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceUser(), constants.DefaultConsistencyChecks, "genesyscloud_user")
log.Printf("Reading user %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
currentUser, resp, getErr := usersAPI.GetUser(d.Id(), []string{
// Expands
"skills",
"languages",
"locations",
"profileSkills",
"certifications",
"employerInfo",
}, "", "")
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to read user %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to read user %s | error: %s", d.Id(), getErr), resp))
}
// Required attributes
d.Set("name", *currentUser.Name)
d.Set("email", *currentUser.Email)
d.Set("division_id", *currentUser.Division.Id)
d.Set("state", *currentUser.State)
if currentUser.Department != nil {
d.Set("department", *currentUser.Department)
} else {
d.Set("department", nil)
}
if currentUser.Title != nil {
d.Set("title", *currentUser.Title)
} else {
d.Set("title", nil)
}
if currentUser.Manager != nil {
d.Set("manager", *(*currentUser.Manager).Id)
} else {
d.Set("manager", nil)
}
if currentUser.AcdAutoAnswer != nil {
d.Set("acd_auto_answer", *currentUser.AcdAutoAnswer)
} else {
d.Set("acd_auto_answer", nil)
}
d.Set("addresses", flattenUserAddresses(d, currentUser.Addresses))
d.Set("routing_skills", flattenUserSkills(currentUser.Skills))
d.Set("routing_languages", flattenUserLanguages(currentUser.Languages))
d.Set("locations", flattenUserLocations(currentUser.Locations))
d.Set("profile_skills", flattenUserProfileSkills(currentUser.ProfileSkills))
d.Set("certifications", flattenUserCertifications(currentUser.Certifications))
d.Set("employer_info", flattenUserEmployerInfo(currentUser.EmployerInfo))
if diagErr := readUserRoutingUtilization(d, sdkConfig); diagErr != nil {
return retry.NonRetryableError(fmt.Errorf("%v", diagErr))
}
log.Printf("Read user %s %s", d.Id(), *currentUser.Email)
return cc.CheckState(d)
})
}
func updateUser(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
email := d.Get("email").(string)
state := d.Get("state").(string)
department := d.Get("department").(string)
title := d.Get("title").(string)
manager := d.Get("manager").(string)
acdAutoAnswer := d.Get("acd_auto_answer").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig)
addresses, err := buildSdkAddresses(d)
if err != nil {
return err
}
log.Printf("Updating user %s", email)
// If state changes, it is the only modifiable field, so it must be updated separately
if d.HasChange("state") {
log.Printf("Updating state for user %s", email)
patchErr := patchUser(d.Id(), platformclientv2.Updateuser{
State: &state,
}, usersAPI)
if patchErr != nil {
return patchErr
}
}
patchErr := patchUser(d.Id(), platformclientv2.Updateuser{
Name: &name,
Email: &email,
Department: &department,
Title: &title,
Manager: &manager,
Addresses: addresses,
Locations: buildSdkLocations(d),
AcdAutoAnswer: &acdAutoAnswer,
Certifications: buildSdkCertifications(d),
EmployerInfo: buildSdkEmployerInfo(d),
}, usersAPI)
if patchErr != nil {
return patchErr
}
diagErr := util.UpdateObjectDivision(d, "USER", sdkConfig)
if diagErr != nil {
return diagErr
}
diagErr = updateUserSkills(d, usersAPI)
if diagErr != nil {
return diagErr
}
diagErr = updateUserLanguages(d, usersAPI)
if diagErr != nil {
return diagErr
}
diagErr = updateUserProfileSkills(d, usersAPI)
if diagErr != nil {
return diagErr
}
diagErr = updateUserRoutingUtilization(d, usersAPI, sdkConfig)
if diagErr != nil {
return diagErr
}
log.Printf("Finished updating user %s", email)
return readUser(ctx, d, meta)
}
func deleteUser(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
email := d.Get("email").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig)
log.Printf("Deleting user %s", email)
err := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Directory occasionally returns version errors on deletes if an object was updated at the same time.
_, resp, err := usersAPI.DeleteUser(d.Id())
if err != nil {
time.Sleep(5 * time.Second)
return resp, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to delete user %s error: %s", d.Id(), err), resp)
}
log.Printf("Deleted user %s", email)
return nil, nil
})
if err != nil {
return err
}
// Verify user in deleted state and search index has been updated
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
id, err := getDeletedUserId(email, usersAPI)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("Error searching for deleted user %s: %v", email, err))
}
if id == nil {
return retry.RetryableError(fmt.Errorf("User %s not yet in deleted state", email))
}
return nil
})
}
func patchUser(id string, update platformclientv2.Updateuser, usersAPI *platformclientv2.UsersApi) diag.Diagnostics {
return patchUserWithState(id, "", update, usersAPI)
}
func patchUserWithState(id string, state string, update platformclientv2.Updateuser, usersAPI *platformclientv2.UsersApi) diag.Diagnostics {
return util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
currentUser, resp, getErr := usersAPI.GetUser(id, nil, "", state)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to read user %s error: %s", id, getErr), resp)
}
update.Version = currentUser.Version
_, resp, patchErr := usersAPI.PatchUser(id, update)
if patchErr != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to update user %s error: %s", id, patchErr), resp)
}
return nil, nil
})
}
func getDeletedUserId(email string, usersAPI *platformclientv2.UsersApi) (*string, diag.Diagnostics) {
exactType := "EXACT"
results, resp, getErr := usersAPI.PostUsersSearch(platformclientv2.Usersearchrequest{
Query: &[]platformclientv2.Usersearchcriteria{
{
Fields: &[]string{"email"},
Value: &email,
VarType: &exactType,
},
{
Fields: &[]string{"state"},
Values: &[]string{"deleted"},
VarType: &exactType,
},
},
})
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to search for user %s error: %s", email, getErr), resp)
}
if results.Results != nil && len(*results.Results) > 0 {
// User found
return (*results.Results)[0].Id, nil
}
return nil, nil
}
func restoreDeletedUser(ctx context.Context, d *schema.ResourceData, meta interface{}, usersAPI *platformclientv2.UsersApi) diag.Diagnostics {
email := d.Get("email").(string)
state := d.Get("state").(string)
log.Printf("Restoring deleted user %s", email)
patchErr := patchUserWithState(d.Id(), "deleted", platformclientv2.Updateuser{
State: &state,
}, usersAPI)
if patchErr != nil {
return patchErr
}
return updateUser(ctx, d, meta)
}
func phoneNumberHash(val interface{}) int {
// Copy map to avoid modifying state
phoneMap := make(map[string]interface{})
for k, v := range val.(map[string]interface{}) {
phoneMap[k] = v
}
if num, ok := phoneMap["number"]; ok {
// Attempt to format phone numbers before hashing
number, err := phonenumbers.Parse(num.(string), "US")
if err == nil {
phoneMap["number"] = phonenumbers.Format(number, phonenumbers.E164)
}
}
return schema.HashResource(phoneNumberResource)(phoneMap)
}
func buildSdkEmails(configEmails *schema.Set) []platformclientv2.Contact {
emailSlice := configEmails.List()
sdkContacts := make([]platformclientv2.Contact, len(emailSlice))
for i, configEmail := range emailSlice {
emailMap := configEmail.(map[string]interface{})
emailAddress, _ := emailMap["address"].(string)
emailType, _ := emailMap["type"].(string)
sdkContacts[i] = platformclientv2.Contact{
Address: &emailAddress,
MediaType: &contactTypeEmail,
VarType: &emailType,
}
}
return sdkContacts
}
func buildSdkPhoneNumbers(configPhoneNumbers *schema.Set) ([]platformclientv2.Contact, diag.Diagnostics) {
phoneNumberSlice := configPhoneNumbers.List()
sdkContacts := make([]platformclientv2.Contact, len(phoneNumberSlice))
for i, configPhone := range phoneNumberSlice {
phoneMap := configPhone.(map[string]interface{})
phoneMediaType := phoneMap["media_type"].(string)
phoneType := phoneMap["type"].(string)
contact := platformclientv2.Contact{
MediaType: &phoneMediaType,
VarType: &phoneType,
}
if phoneNum, ok := phoneMap["number"].(string); ok && phoneNum != "" {
contact.Address = &phoneNum
}
if phoneExt, ok := phoneMap["extension"].(string); ok && phoneExt != "" {
contact.Extension = &phoneExt
}
sdkContacts[i] = contact
}
return sdkContacts, nil
}
func buildSdkAddresses(d *schema.ResourceData) (*[]platformclientv2.Contact, diag.Diagnostics) {
if addresses := d.Get("addresses").([]interface{}); addresses != nil {
sdkAddresses := make([]platformclientv2.Contact, 0)
var otherEmails *schema.Set
var phoneNumbers *schema.Set
if len(addresses) > 0 {
if addressMap, ok := addresses[0].(map[string]interface{}); ok {
otherEmails = addressMap["other_emails"].(*schema.Set)
phoneNumbers = addressMap["phone_numbers"].(*schema.Set)
} else {
return nil, nil
}
}
if otherEmails != nil {
sdkAddresses = append(sdkAddresses, buildSdkEmails(otherEmails)...)
}
if phoneNumbers != nil {
sdkNums, err := buildSdkPhoneNumbers(phoneNumbers)
if err != nil {
return nil, err
}
sdkAddresses = append(sdkAddresses, sdkNums...)
}
return &sdkAddresses, nil
}
return nil, nil
}
func buildSdkLocations(d *schema.ResourceData) *[]platformclientv2.Location {
if locationConfig := d.Get("locations"); locationConfig != nil {
sdkLocations := make([]platformclientv2.Location, 0)
locationList := locationConfig.(*schema.Set).List()
for _, configLoc := range locationList {
locMap := configLoc.(map[string]interface{})
locID := locMap["location_id"].(string)
locNotes := locMap["notes"].(string)
sdkLocations = append(sdkLocations, platformclientv2.Location{
Id: &locID,
Notes: &locNotes,
})
}
return &sdkLocations
}
return nil
}
func buildSdkEmployerInfo(d *schema.ResourceData) *platformclientv2.Employerinfo {
if configInfo := d.Get("employer_info").([]interface{}); configInfo != nil {
var sdkInfo platformclientv2.Employerinfo
if len(configInfo) > 0 && configInfo[0] != nil {
if _, ok := configInfo[0].(map[string]interface{}); !ok {
return nil
}
infoMap := configInfo[0].(map[string]interface{})
// Only set non-empty values.
if offName := infoMap["official_name"].(string); len(offName) > 0 {
sdkInfo.OfficialName = &offName
}
if empID := infoMap["employee_id"].(string); len(empID) > 0 {
sdkInfo.EmployeeId = &empID
}
if empType := infoMap["employee_type"].(string); len(empType) > 0 {
sdkInfo.EmployeeType = &empType
}
if dateHire := infoMap["date_hire"].(string); len(dateHire) > 0 {
sdkInfo.DateHire = &dateHire
}
}
return &sdkInfo
}
return nil
}
func buildSdkCertifications(d *schema.ResourceData) *[]string {
if certs := d.Get("certifications"); certs != nil {
return lists.SetToStringList(certs.(*schema.Set))
}
return nil
}
func getNumbers(d *schema.ResourceData, index int) (bool, bool) {
isNumber := false
isExtension := false
if addresses1 := d.Get("addresses").([]interface{}); addresses1 != nil {
var phoneNumbers *schema.Set
if len(addresses1) > 0 {
addressMap := addresses1[0].(map[string]interface{})
phoneNumbers = addressMap["phone_numbers"].(*schema.Set)
}
if phoneNumbers != nil {
phoneNumberSlice := phoneNumbers.List()
for ii, configPhone := range phoneNumberSlice {
if ii != index {
continue
}
phoneMap := configPhone.(map[string]interface{})
if phoneNum, ok := phoneMap["number"].(string); ok && phoneNum != "" {
isNumber = true
}
if phoneExt, ok := phoneMap["extension"].(string); ok && phoneExt != "" {
isExtension = true
}
break
}
}
}
return isNumber, isExtension
}
func flattenUserAddresses(d *schema.ResourceData, addresses *[]platformclientv2.Contact) []interface{} {
if addresses == nil || len(*addresses) == 0 {
return nil
}
emailSet := schema.NewSet(schema.HashResource(otherEmailResource), []interface{}{})
phoneNumSet := schema.NewSet(phoneNumberHash, []interface{}{})
for i, address := range *addresses {
if address.MediaType != nil {
if *address.MediaType == "SMS" || *address.MediaType == "PHONE" {
phoneNumber := make(map[string]interface{})
phoneNumber["media_type"] = *address.MediaType
// Strip off any parentheses from phone numbers
if address.Address != nil {
phoneNumber["number"] = strings.Trim(*address.Address, "()")
} else if address.Display != nil {
// Some numbers are only returned in Display
isNumber, isExtension := getNumbers(d, i)
if isNumber && phoneNumber["number"] != "" {
phoneNumber["number"] = strings.Trim(*address.Display, "()")
}
if isExtension {
phoneNumber["extension"] = strings.Trim(*address.Display, "()")
}
if !isNumber && !isExtension {
if address.Extension == nil {
phoneNumber["extension"] = strings.Trim(*address.Display, "()")
} else if phoneNumber["number"] != "" {
phoneNumber["number"] = strings.Trim(*address.Display, "()")
}
}
}
if address.Extension != nil {
phoneNumber["extension"] = *address.Extension
}
if address.VarType != nil {
phoneNumber["type"] = *address.VarType
}
phoneNumSet.Add(phoneNumber)
} else if *address.MediaType == "EMAIL" {
email := make(map[string]interface{})
email["type"] = *address.VarType
email["address"] = *address.Address
emailSet.Add(email)
} else {
log.Printf("Unknown address media type %s", *address.MediaType)
}
}
}
return []interface{}{map[string]interface{}{
"other_emails": emailSet,
"phone_numbers": phoneNumSet,
}}
}
func flattenUserEmployerInfo(empInfo *platformclientv2.Employerinfo) []interface{} {
if empInfo == nil {
return nil
}
var (
offName string
empID string
empType string
dateHire string
)
if empInfo.OfficialName != nil {
offName = *empInfo.OfficialName
}
if empInfo.EmployeeId != nil {
empID = *empInfo.EmployeeId
}
if empInfo.EmployeeType != nil {
empType = *empInfo.EmployeeType
}
if empInfo.DateHire != nil {
dateHire = *empInfo.DateHire
}
return []interface{}{map[string]interface{}{
"official_name": offName,
"employee_id": empID,
"employee_type": empType,
"date_hire": dateHire,
}}
}
func readUserRoutingUtilization(d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
log.Printf("Getting user utilization")
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
apiClient := &routingAPI.Configuration.APIClient
path := fmt.Sprintf("%s/api/v2/routing/users/%s/utilization", routingAPI.Configuration.BasePath, d.Id())
headerParams := buildHeaderParams(routingAPI)
response, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil)
if err != nil {
if util.IsStatus404(response) {
d.SetId("") // User doesn't exist
return nil
}
return util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to read routing utilization for user %s error: %s", d.Id(), err), response)
}
agentUtilization := &AgentUtilizationWithLabels{}
json.Unmarshal(response.RawBody, &agentUtilization)
if agentUtilization == nil {
d.Set("routing_utilization", nil)
} else if agentUtilization.Level == "Organization" {
// If the settings are org-wide, set to empty to indicate no settings on the user
d.Set("routing_utilization", []interface{}{})
} else {
allSettings := map[string]interface{}{}
if agentUtilization.Utilization != nil {
for sdkType, schemaType := range utilizationMediaTypes {
if mediaSettings, ok := agentUtilization.Utilization[sdkType]; ok {
allSettings[schemaType] = flattenUtilizationSetting(mediaSettings)
}
}
}
if agentUtilization.LabelUtilizations != nil {
utilConfig := d.Get("routing_utilization").([]interface{})
if utilConfig != nil && len(utilConfig) > 0 && utilConfig[0] != nil {
originalSettings := utilConfig[0].(map[string]interface{})
originalLabelUtilizations := originalSettings["label_utilizations"].([]interface{})
// Only add to the state the configured labels, in the configured order, but not any extras, to help terraform with matching new and old state.
filteredLabelUtilizations := filterAndFlattenLabelUtilizations(agentUtilization.LabelUtilizations, originalLabelUtilizations)
allSettings["label_utilizations"] = filteredLabelUtilizations
} else {
allSettings["label_utilizations"] = make([]interface{}, 0)
}
}
d.Set("routing_utilization", []interface{}{allSettings})
}
return nil
}
func updateUserSkills(d *schema.ResourceData, usersAPI *platformclientv2.UsersApi) diag.Diagnostics {
transformFunc := func(configSkill interface{}) platformclientv2.Userroutingskillpost {
skillMap := configSkill.(map[string]interface{})
skillID := skillMap["skill_id"].(string)
skillProf := skillMap["proficiency"].(float64)
return platformclientv2.Userroutingskillpost{
Id: &skillID,
Proficiency: &skillProf,
}
}
chunkProcessor := func(chunk []platformclientv2.Userroutingskillpost) diag.Diagnostics {
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
_, resp, err := usersAPI.PatchUserRoutingskillsBulk(d.Id(), chunk)
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to update skills for user %s error: %s", d.Id(), err), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
return nil
}
if d.HasChange("routing_skills") {
if skillsConfig := d.Get("routing_skills"); skillsConfig != nil {
skillsList := skillsConfig.(*schema.Set).List()
chunks := chunksProcess.ChunkItems(skillsList, transformFunc, 50)
return chunksProcess.ProcessChunks(chunks, chunkProcessor)
}
}
return nil
}
func updateUserLanguages(d *schema.ResourceData, usersAPI *platformclientv2.UsersApi) diag.Diagnostics {
if d.HasChange("routing_languages") {
if languages := d.Get("routing_languages"); languages != nil {
log.Printf("Updating languages for user %s", d.Get("email"))
newLangProfs := make(map[string]int)
langList := languages.(*schema.Set).List()
newLangIds := make([]string, len(langList))
for i, lang := range langList {
langMap := lang.(map[string]interface{})
newLangIds[i] = langMap["language_id"].(string)
newLangProfs[newLangIds[i]] = langMap["proficiency"].(int)
}
oldSdkLangs, err := getUserRoutingLanguages(d.Id(), usersAPI)
if err != nil {
return err
}
oldLangIds := make([]string, len(oldSdkLangs))
oldLangProfs := make(map[string]int)
for i, lang := range oldSdkLangs {
oldLangIds[i] = *lang.Id
oldLangProfs[oldLangIds[i]] = int(*lang.Proficiency)
}
if len(oldLangIds) > 0 {
langsToRemove := lists.SliceDifference(oldLangIds, newLangIds)
for _, langID := range langsToRemove {
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
resp, err := usersAPI.DeleteUserRoutinglanguage(d.Id(), langID)
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to remove language from user %s error: %s", d.Id(), err), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
}
}
if len(newLangIds) > 0 {
// Languages to add
langsToAddOrUpdate := lists.SliceDifference(newLangIds, oldLangIds)
// Check for existing proficiencies to update which can be done with the same API
for langID, newNum := range newLangProfs {
if oldNum, found := oldLangProfs[langID]; found {
if newNum != oldNum {
langsToAddOrUpdate = append(langsToAddOrUpdate, langID)
}
}
}
if diagErr := updateUserRoutingLanguages(d.Id(), langsToAddOrUpdate, newLangProfs, usersAPI); diagErr != nil {
return diagErr
}
}
log.Printf("Languages updated for user %s", d.Get("email"))
}
}
return nil
}
func getUserRoutingLanguages(userID string, api *platformclientv2.UsersApi) ([]platformclientv2.Userroutinglanguage, diag.Diagnostics) {
const maxPageSize = 50
var sdkLanguages []platformclientv2.Userroutinglanguage
for pageNum := 1; ; pageNum++ {
langs, resp, err := api.GetUserRoutinglanguages(userID, maxPageSize, pageNum, "")
if err != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to query languages for user %s error: %s", userID, err), resp)
}
if langs == nil || langs.Entities == nil || len(*langs.Entities) == 0 {
return sdkLanguages, nil
}
for _, language := range *langs.Entities {
sdkLanguages = append(sdkLanguages, language)
}
}
}
func updateUserRoutingLanguages(
userID string,
langsToUpdate []string,
langProfs map[string]int,
api *platformclientv2.UsersApi) diag.Diagnostics {
// Bulk API restricts language adds to 50 per call
const maxBatchSize = 50
chunkBuild := func(val string) platformclientv2.Userroutinglanguagepost {
newProf := float64(langProfs[val])
return platformclientv2.Userroutinglanguagepost{
Id: &val,
Proficiency: &newProf,
}
}
// Generic call to prepare chunks for the Update. Takes in three args
// 1. langsToUpdate 2. The Entity prepare func for the update 3. Chunk Size
chunks := chunksProcess.ChunkItems(langsToUpdate, chunkBuild, maxBatchSize)
// Closure to process the chunks
chunkProcessor := func(chunk []platformclientv2.Userroutinglanguagepost) diag.Diagnostics {
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
_, resp, err := api.PatchUserRoutinglanguagesBulk(userID, chunk)
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to update languages for user %s error: %s", userID, err), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
return nil
}
// Genric Function call which takes in the chunks and the processing function
return chunksProcess.ProcessChunks(chunks, chunkProcessor)
}
func updateUserProfileSkills(d *schema.ResourceData, usersAPI *platformclientv2.UsersApi) diag.Diagnostics {
if d.HasChange("profile_skills") {
if profileSkills := d.Get("profile_skills"); profileSkills != nil {
profileSkills := lists.SetToStringList(profileSkills.(*schema.Set))
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
_, resp, err := usersAPI.PutUserProfileskills(d.Id(), *profileSkills)
if err != nil {
return resp, util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to update profile skills for user %s error: %s", d.Id(), err), resp)
}
return nil, nil
})
if diagErr != nil {
return diagErr
}
}
}
return nil
}
func updateUserRoutingUtilization(d *schema.ResourceData, usersAPI *platformclientv2.UsersApi, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
if d.HasChange("routing_utilization") {
if utilConfig := d.Get("routing_utilization").([]interface{}); utilConfig != nil {
var err error
log.Printf("Updating user utilization for user %s", d.Id())
if len(utilConfig) > 0 && utilConfig[0] != nil { // Specified but empty utilization list will reset to org-wide defaults
// Update settings
allSettings := utilConfig[0].(map[string]interface{})
labelUtilizations := allSettings["label_utilizations"].([]interface{})
if labelUtilizations != nil && len(labelUtilizations) > 0 {
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
apiClient := &routingAPI.Configuration.APIClient
path := fmt.Sprintf("%s/api/v2/routing/users/%s/utilization", routingAPI.Configuration.BasePath, d.Id())
headerParams := buildHeaderParams(routingAPI)
requestPayload := make(map[string]interface{})
requestPayload["utilization"] = buildMediaTypeUtilizations(allSettings)
requestPayload["labelUtilizations"] = buildLabelUtilizationsRequest(labelUtilizations)
_, err = apiClient.CallAPI(path, "PUT", requestPayload, headerParams, nil, nil, "", nil)
} else {
sdkSettings := make(map[string]platformclientv2.Mediautilization)
for sdkType, schemaType := range utilizationMediaTypes {
if mediaSettings, ok := allSettings[schemaType]; ok && len(mediaSettings.([]interface{})) > 0 {
sdkSettings[sdkType] = buildSdkMediaUtilization(mediaSettings.([]interface{}))
}
}
_, _, err = usersAPI.PutRoutingUserUtilization(d.Id(), platformclientv2.Utilizationrequest{
Utilization: &sdkSettings,
})
}
if err != nil {
return util.BuildDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to update Routing Utilization for user %s", d.Id()), err)
}
} else {
// Reset to org-wide defaults
resp, err := usersAPI.DeleteRoutingUserUtilization(d.Id())
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_user", fmt.Sprintf("Failed to delete routing utilization for user %s error: %s", d.Id(), err), resp)
}
}
log.Printf("Updated user utilization for user %s", d.Id())
}
}
return nil
}
func flattenUserSkills(skills *[]platformclientv2.Userroutingskill) *schema.Set {
if skills == nil {
return nil
}
skillSet := schema.NewSet(schema.HashResource(userSkillResource), []interface{}{})
for _, sdkSkill := range *skills {
skill := make(map[string]interface{})
skill["skill_id"] = *sdkSkill.Id
skill["proficiency"] = *sdkSkill.Proficiency
skillSet.Add(skill)
}
return skillSet
}
func flattenUserLanguages(languages *[]platformclientv2.Userroutinglanguage) *schema.Set {
if languages == nil {
return nil
}
languageSet := schema.NewSet(schema.HashResource(userLanguageResource), []interface{}{})
for _, sdkLang := range *languages {
language := make(map[string]interface{})
language["language_id"] = *sdkLang.Id
language["proficiency"] = int(*sdkLang.Proficiency)
languageSet.Add(language)
}
return languageSet
}
func flattenUserLocations(locations *[]platformclientv2.Location) *schema.Set {
if locations == nil {
return nil
}
locSet := schema.NewSet(schema.HashResource(userLocationResource), []interface{}{})
for _, sdkLoc := range *locations {
if sdkLoc.LocationDefinition != nil {
location := make(map[string]interface{})
location["location_id"] = *sdkLoc.LocationDefinition.Id
if sdkLoc.Notes != nil {
location["notes"] = *sdkLoc.Notes
}
locSet.Add(location)
}
}
return locSet
}
func flattenUserProfileSkills(skills *[]string) *schema.Set {
if skills != nil {
return lists.StringListToSet(*skills)
}
return nil
}
func flattenUserCertifications(certs *[]string) *schema.Set {
if certs != nil {
return lists.StringListToSet(*certs)
}
return nil
}
func buildMediaTypeUtilizations(allUtilizations map[string]interface{}) *map[string]platformclientv2.Mediautilization {
settings := make(map[string]platformclientv2.Mediautilization)
for sdkType, schemaType := range utilizationMediaTypes {
mediaSettings := allUtilizations[schemaType].([]interface{})
if mediaSettings != nil && len(mediaSettings) > 0 {
settings[sdkType] = buildSdkMediaUtilization(mediaSettings)
}
}
return &settings
}
// Basic user with minimum required fields
func GenerateBasicUserResource(resourceID string, email string, name string) string {
return GenerateUserResource(resourceID, email, name, util.NullValue, util.NullValue, util.NullValue, util.NullValue, util.NullValue, "", "")
}
func GenerateUserResource(
resourceID string,
email string,
name string,
state string,
title string,
department string,
manager string,
acdAutoAnswer string,
profileSkills string,
certifications string) string {
return fmt.Sprintf(`resource "genesyscloud_user" "%s" {
email = "%s"
name = "%s"
state = %s
title = %s
department = %s
manager = %s
acd_auto_answer = %s
profile_skills = [%s]
certifications = [%s]
}
`, resourceID, email, name, state, title, department, manager, acdAutoAnswer, profileSkills, certifications)
}
package genesyscloud
import (
"context"
"fmt"
"log"
"net/url"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
const (
V1 = "v1"
V1HTTP = "v1-http"
V2 = "v2"
THIRDPARTY = "third-party"
HTTPSPROTOCOL = "https"
WEBSKINBASIC = "basic"
WEBSKINMODERN = "modern-caret-skin"
)
var (
clientConfigSchemaResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"webchat_skin": {
Description: "Skin for the webchat user. (basic, modern-caret-skin)",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{WEBSKINBASIC, WEBSKINMODERN}, false),
},
"authentication_url": {
Description: "Url endpoint to perform_authentication",
Type: schema.TypeString,
Required: false,
Optional: true,
ValidateDiagFunc: validateAuthURL,
},
},
}
)
func getAllWidgetDeployments(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
widgetsAPI := platformclientv2.NewWidgetsApiWithConfig(clientConfig)
widgetDeployments, resp, getErr := widgetsAPI.GetWidgetsDeployments()
if getErr != nil {
return nil, util.BuildAPIDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed to get page of widget deployment error: %s", getErr), resp)
}
for _, widgetDeployment := range *widgetDeployments.Entities {
resources[*widgetDeployment.Id] = &resourceExporter.ResourceMeta{Name: *widgetDeployment.Name}
}
return resources, nil
}
func buildSdkAllowedDomains(d *schema.ResourceData) *[]string {
allowed_domains := []string{}
if domains, ok := d.GetOk("allowed_domains"); ok {
allowed_domains = lists.InterfaceListToStrings(domains.([]interface{}))
}
return &allowed_domains
}
func parseSdkClientConfigData(d *schema.ResourceData) (webchatSkin *string, authenticationUrl *string) {
clientConfigSet := d.Get("client_config").(*schema.Set)
if clientConfigSet != nil && len(clientConfigSet.List()) > 0 {
clientConfig := clientConfigSet.List()[0].(map[string]interface{})
fields := make(map[string]string)
for k, v := range clientConfig {
fields[k] = v.(string)
}
webchatSkin := fields["webchat_skin"]
authUrl := fields["authentication_url"]
return &webchatSkin, &authUrl
}
return nil, nil
}
func validateAuthURL(authUrl interface{}, _ cty.Path) diag.Diagnostics {
authUrlString := authUrl.(string)
u, err := url.Parse(authUrlString)
if err != nil {
return util.BuildDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Authorization url %s provided is not a valid URL", authUrlString), err)
}
if u.Scheme == "" || u.Host == "" {
log.Printf("Scheme: %s", u.Scheme)
log.Printf("Host: %s", u.Host)
return util.BuildDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Authorization url %s provided is not valid url", authUrlString), fmt.Errorf("authorization url provided is not valid url"))
}
if u.Scheme != HTTPSPROTOCOL {
return util.BuildDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Authorization url provided must begin with https"), fmt.Errorf("authorization url provided must begin with https"))
}
return nil
}
func buildSDKClientConfig(clientType string, d *schema.ResourceData) (*platformclientv2.Widgetclientconfig, error) {
widgetClientConfig := &platformclientv2.Widgetclientconfig{}
clientConfig := d.Get("client_config").(*schema.Set)
if (clientType == V1 || clientType == V1HTTP) && clientConfig.Len() == 0 {
return nil, fmt.Errorf("V1 and v1-http widget configurations must have a client_config defined. ")
}
if clientType == V1 {
v1Client := &platformclientv2.Widgetclientconfigv1{}
v1Client.WebChatSkin, v1Client.AuthenticationUrl = parseSdkClientConfigData(d)
widgetClientConfig.V1 = v1Client
}
if clientType == V1HTTP {
v1HttpClient := &platformclientv2.Widgetclientconfigv1http{}
v1HttpClient.WebChatSkin, v1HttpClient.AuthenticationUrl = parseSdkClientConfigData(d)
widgetClientConfig.V1Http = v1HttpClient
}
return widgetClientConfig, nil
}
func WidgetDeploymentExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllWidgetDeployments),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"flow_id": {RefType: "genesyscloud_flow"},
},
}
}
func ResourceWidgetDeployment() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Widget Deployment",
CreateContext: provider.CreateWithPooledClient(createWidgetDeployment),
ReadContext: provider.ReadWithPooledClient(readWidgetDeployment),
UpdateContext: provider.UpdateWithPooledClient(updateWidgetDeployment),
DeleteContext: provider.DeleteWithPooledClient(deleteWidgetDeployment),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of the Widget Deployment.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "Widget Deployment description.",
Type: schema.TypeString,
Optional: true,
},
"authentication_required": {
Description: "When true, the customer members starting a chat must be authenticated by supplying their JWT to the create operation.",
Type: schema.TypeBool,
Required: true,
},
"disabled": {
Description: "When true, all create chat operations using this Deployment will be rejected.",
Type: schema.TypeBool,
Required: true,
},
"flow_id": {
Description: "The Inbound Chat Flow to run when new chats are initiated under this Deployment",
Type: schema.TypeString,
Optional: true,
},
"allowed_domains": {
Description: "The list of domains that are approved to use this Deployment; the list will be added to CORS headers for ease of web use",
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Required: false,
Optional: true,
},
"client_type": {
Description: "The type of display widget for which this Deployment is configured, which controls the administrator settings shown.Valid values: v1, v2, v1-http, third-party.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{V1, V2, V1HTTP, THIRDPARTY}, false),
},
"client_config": {
Description: " The V1 and V1-http client configuration options that should be made available to the clients of this Deployment.",
Type: schema.TypeSet,
MaxItems: 1,
Optional: true,
Elem: clientConfigSchemaResource,
},
},
}
}
func flattenClientConfig(clientType string, clientConfig platformclientv2.Widgetclientconfig) *schema.Set {
clientConfigSet := schema.NewSet(schema.HashResource(clientConfigSchemaResource), []interface{}{})
clientConfigMap := make(map[string]interface{})
if clientType == V1 {
if clientConfig.V1.WebChatSkin != nil {
clientConfigMap["webchat_skin"] = *clientConfig.V1.WebChatSkin
}
if clientConfig.V1.AuthenticationUrl != nil {
clientConfigMap["authentication_url"] = *clientConfig.V1.AuthenticationUrl
}
}
if clientType == V1HTTP {
if clientConfig.V1Http.WebChatSkin != nil {
clientConfigMap["webchat_skin"] = *clientConfig.V1Http.WebChatSkin
}
if clientConfig.V1Http.AuthenticationUrl != nil {
clientConfigMap["authentication_url"] = *clientConfig.V1Http.AuthenticationUrl
}
}
clientConfigSet.Add(clientConfigMap)
return clientConfigSet
}
func readWidgetDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
widgetsAPI := platformclientv2.NewWidgetsApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceWidgetDeployment(), constants.DefaultConsistencyChecks, "genesyscloud_widget_deployment")
log.Printf("Reading widget deployment %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
currentWidget, resp, getErr := widgetsAPI.GetWidgetsDeployment(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed to read widget deployment %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed to read widget deployment %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *currentWidget.Name)
if currentWidget.Description != nil {
d.Set("description", *currentWidget.Description)
} else {
d.Set("description", nil)
}
if currentWidget.AuthenticationRequired != nil {
d.Set("authentication_required", *currentWidget.AuthenticationRequired)
} else {
d.Set("authentication_required", nil)
}
if currentWidget.Disabled != nil {
d.Set("disabled", *currentWidget.Disabled)
} else {
d.Set("disabled", nil)
}
if currentWidget.Flow != nil {
d.Set("flow_id", *currentWidget.Flow.Id)
} else {
d.Set("flow_id", nil)
}
if currentWidget.AllowedDomains != nil {
d.Set("allowed_domains", *currentWidget.AllowedDomains)
} else {
d.Set("allowed_domains", nil)
}
if currentWidget.ClientType != nil {
d.Set("client_type", *currentWidget.ClientType)
} else {
d.Set("client_type", nil)
}
if currentWidget.ClientConfig != nil {
d.Set("client_config", flattenClientConfig(*currentWidget.ClientType, *currentWidget.ClientConfig))
} else {
d.Set("client_config", nil)
}
return cc.CheckState(d)
})
}
func createWidgetDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
auth_required := d.Get("authentication_required").(bool)
disabled := d.Get("disabled").(bool)
flowId := util.BuildSdkDomainEntityRef(d, "flow_id")
allowed_domains := buildSdkAllowedDomains(d) //Need to make this an array of strings.
client_type := d.Get("client_type").(string)
client_config, client_config_err := buildSDKClientConfig(client_type, d)
if client_config_err != nil {
return util.BuildDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed to create widget deployment %s", name), client_config_err)
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
widgetsAPI := platformclientv2.NewWidgetsApiWithConfig(sdkConfig)
createWidget := platformclientv2.Widgetdeployment{
Name: &name,
Description: &description,
AuthenticationRequired: &auth_required,
Disabled: &disabled,
Flow: flowId,
AllowedDomains: allowed_domains,
ClientType: &client_type,
ClientConfig: client_config,
}
log.Printf("Creating widgets deployment %s", name)
// Get all existing deployments
resourceIDMetaMap, _ := getAllWidgetDeployments(ctx, sdkConfig)
widget, resp, err := widgetsAPI.PostWidgetsDeployments(createWidget)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed to create widget deployment %s error: %s", name, err), resp)
}
log.Printf("Widget created %s with id %s", name, *widget.Id)
d.SetId(*widget.Id)
time.Sleep(2 * time.Second)
// Get all new deployments
newResourceIDMetaMap, _ := getAllWidgetDeployments(ctx, sdkConfig)
// Delete potential duplicates
deletePotentialDuplicateDeployments(widgetsAPI, name, *widget.Id, resourceIDMetaMap, newResourceIDMetaMap)
return readWidgetDeployment(ctx, d, meta)
}
// Sometimes the Widget API creates 2 deployments due to a bug. This function will delete any duplicates
func deletePotentialDuplicateDeployments(widgetAPI *platformclientv2.WidgetsApi, name, id string, existingResourceIDMetaMap, newResourceIDMetaMap resourceExporter.ResourceIDMetaMap) {
for _, val := range existingResourceIDMetaMap {
for key1, val1 := range newResourceIDMetaMap {
if val.Name == val1.Name {
delete(newResourceIDMetaMap, key1)
break
}
}
}
for key, val := range newResourceIDMetaMap {
if key != id && val.Name == name {
log.Printf("Deleting duplicate widget deployment %s", name)
_, err := widgetAPI.DeleteWidgetsDeployment(key)
if err != nil {
log.Printf("failed to delete widget deployment %s: %s", name, err)
}
}
}
}
func deleteWidgetDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
widgetAPI := platformclientv2.NewWidgetsApiWithConfig(sdkConfig)
log.Printf("Deleting widget deployment %s", name)
resp, err := widgetAPI.DeleteWidgetsDeployment(d.Id())
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed to delete widget deployment %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := widgetAPI.GetWidgetsDeployment(d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Widget deployment %s deleted", name)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Error deleting widget deployment %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Widget deployment %s still exists", d.Id()), resp))
})
}
func updateWidgetDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
auth_required := d.Get("authentication_required").(bool)
disabled := d.Get("disabled").(bool)
flowId := util.BuildSdkDomainEntityRef(d, "flow_id")
allowed_domains := buildSdkAllowedDomains(d) //Need to make this an array of strings.
client_type := d.Get("client_type").(string)
client_config, client_config_err := buildSDKClientConfig(client_type, d)
if client_config_err != nil {
return util.BuildDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed updating widget deployment %s", name), client_config_err)
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
widgetsAPI := platformclientv2.NewWidgetsApiWithConfig(sdkConfig)
updateWidget := platformclientv2.Widgetdeployment{
Name: &name,
Description: &description,
AuthenticationRequired: &auth_required,
Disabled: &disabled,
Flow: flowId,
AllowedDomains: allowed_domains,
ClientType: &client_type,
ClientConfig: client_config,
}
log.Printf("Updating widget deployment %s", name)
widget, resp, err := widgetsAPI.PutWidgetsDeployment(d.Id(), updateWidget)
if err != nil {
return util.BuildAPIDiagnosticError("genesyscloud_widget_deployment", fmt.Sprintf("Failed to update widget deployment %s error: %s", name, err), resp)
}
d.SetId(*widget.Id)
log.Printf("Finished updating widget deployment %s", name)
return readWidgetDeployment(ctx, d, meta)
}
package responsemanagement_library
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_responsemanagement_library.go contains the data source implementation
for the resource.
*/
// dataSourceResponsemanagementLibraryRead retrieves by name the id in question
func dataSourceResponsemanagementLibraryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newResponsemanagementLibraryProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
libraryId, retryable, resp, err := proxy.getResponsemanagementLibraryIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching responsemanagement library %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No responsemanagement library found with name %s", name), resp))
}
d.SetId(libraryId)
return nil
})
}
package responsemanagement_library
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
)
/*
The genesyscloud_responsemanagement_library_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *responsemanagementLibraryProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createResponsemanagementLibraryFunc func(ctx context.Context, p *responsemanagementLibraryProxy, library *platformclientv2.Library) (*platformclientv2.Library, *platformclientv2.APIResponse, error)
type getAllResponsemanagementLibraryFunc func(ctx context.Context, p *responsemanagementLibraryProxy, name string) (*[]platformclientv2.Library, *platformclientv2.APIResponse, error)
type getResponsemanagementLibraryIdByNameFunc func(ctx context.Context, p *responsemanagementLibraryProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getResponsemanagementLibraryByIdFunc func(ctx context.Context, p *responsemanagementLibraryProxy, id string) (library *platformclientv2.Library, response *platformclientv2.APIResponse, err error)
type updateResponsemanagementLibraryFunc func(ctx context.Context, p *responsemanagementLibraryProxy, id string, library *platformclientv2.Library) (*platformclientv2.Library, *platformclientv2.APIResponse, error)
type deleteResponsemanagementLibraryFunc func(ctx context.Context, p *responsemanagementLibraryProxy, id string) (response *platformclientv2.APIResponse, err error)
// responsemanagementLibraryProxy contains all of the methods that call genesys cloud APIs.
type responsemanagementLibraryProxy struct {
clientConfig *platformclientv2.Configuration
responseManagementApi *platformclientv2.ResponseManagementApi
createResponsemanagementLibraryAttr createResponsemanagementLibraryFunc
getAllResponsemanagementLibraryAttr getAllResponsemanagementLibraryFunc
getResponsemanagementLibraryIdByNameAttr getResponsemanagementLibraryIdByNameFunc
getResponsemanagementLibraryByIdAttr getResponsemanagementLibraryByIdFunc
updateResponsemanagementLibraryAttr updateResponsemanagementLibraryFunc
deleteResponsemanagementLibraryAttr deleteResponsemanagementLibraryFunc
}
// newResponsemanagementLibraryProxy initializes the responsemanagement library proxy with all of the data needed to communicate with Genesys Cloud
func newResponsemanagementLibraryProxy(clientConfig *platformclientv2.Configuration) *responsemanagementLibraryProxy {
api := platformclientv2.NewResponseManagementApiWithConfig(clientConfig)
return &responsemanagementLibraryProxy{
clientConfig: clientConfig,
responseManagementApi: api,
createResponsemanagementLibraryAttr: createResponsemanagementLibraryFn,
getAllResponsemanagementLibraryAttr: getAllResponsemanagementLibraryFn,
getResponsemanagementLibraryIdByNameAttr: getResponsemanagementLibraryIdByNameFn,
getResponsemanagementLibraryByIdAttr: getResponsemanagementLibraryByIdFn,
updateResponsemanagementLibraryAttr: updateResponsemanagementLibraryFn,
deleteResponsemanagementLibraryAttr: deleteResponsemanagementLibraryFn,
}
}
// getResponsemanagementLibraryProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getResponsemanagementLibraryProxy(clientConfig *platformclientv2.Configuration) *responsemanagementLibraryProxy {
if internalProxy == nil {
internalProxy = newResponsemanagementLibraryProxy(clientConfig)
}
return internalProxy
}
// createResponsemanagementLibrary creates a Genesys Cloud responsemanagement library
func (p *responsemanagementLibraryProxy) createResponsemanagementLibrary(ctx context.Context, responsemanagementLibrary *platformclientv2.Library) (*platformclientv2.Library, *platformclientv2.APIResponse, error) {
return p.createResponsemanagementLibraryAttr(ctx, p, responsemanagementLibrary)
}
// getResponsemanagementLibrary retrieves all Genesys Cloud responsemanagement library
func (p *responsemanagementLibraryProxy) getAllResponsemanagementLibrary(ctx context.Context) (*[]platformclientv2.Library, *platformclientv2.APIResponse, error) {
return p.getAllResponsemanagementLibraryAttr(ctx, p, "")
}
// getResponsemanagementLibraryIdByName returns a single Genesys Cloud responsemanagement library by a name
func (p *responsemanagementLibraryProxy) getResponsemanagementLibraryIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getResponsemanagementLibraryIdByNameAttr(ctx, p, name)
}
// getResponsemanagementLibraryById returns a single Genesys Cloud responsemanagement library by Id
func (p *responsemanagementLibraryProxy) getResponsemanagementLibraryById(ctx context.Context, id string) (responsemanagementLibrary *platformclientv2.Library, response *platformclientv2.APIResponse, err error) {
return p.getResponsemanagementLibraryByIdAttr(ctx, p, id)
}
// updateResponsemanagementLibrary updates a Genesys Cloud responsemanagement library
func (p *responsemanagementLibraryProxy) updateResponsemanagementLibrary(ctx context.Context, id string, responsemanagementLibrary *platformclientv2.Library) (*platformclientv2.Library, *platformclientv2.APIResponse, error) {
return p.updateResponsemanagementLibraryAttr(ctx, p, id, responsemanagementLibrary)
}
// deleteResponsemanagementLibrary deletes a Genesys Cloud responsemanagement library by Id
func (p *responsemanagementLibraryProxy) deleteResponsemanagementLibrary(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteResponsemanagementLibraryAttr(ctx, p, id)
}
// createResponsemanagementLibraryFn is an implementation function for creating a Genesys Cloud responsemanagement library
func createResponsemanagementLibraryFn(ctx context.Context, p *responsemanagementLibraryProxy, responsemanagementLibrary *platformclientv2.Library) (*platformclientv2.Library, *platformclientv2.APIResponse, error) {
library, resp, err := p.responseManagementApi.PostResponsemanagementLibraries(*responsemanagementLibrary)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create responsemanagement library: %s", err)
}
return library, resp, nil
}
// getAllResponsemanagementLibraryFn is the implementation for retrieving all responsemanagement library in Genesys Cloud
func getAllResponsemanagementLibraryFn(ctx context.Context, p *responsemanagementLibraryProxy, name string) (*[]platformclientv2.Library, *platformclientv2.APIResponse, error) {
var allLibrarys []platformclientv2.Library
const pageSize = 100
librarys, resp, err := p.responseManagementApi.GetResponsemanagementLibraries(1, pageSize, "", name)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get library: %v", err)
}
if librarys.Entities == nil || len(*librarys.Entities) == 0 {
return &allLibrarys, resp, nil
}
for _, library := range *librarys.Entities {
allLibrarys = append(allLibrarys, library)
}
for pageNum := 2; pageNum <= *librarys.PageCount; pageNum++ {
librarys, resp, err := p.responseManagementApi.GetResponsemanagementLibraries(pageNum, pageSize, "", name)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get library: %v", err)
}
if librarys.Entities == nil || len(*librarys.Entities) == 0 {
break
}
for _, library := range *librarys.Entities {
allLibrarys = append(allLibrarys, library)
}
}
return &allLibrarys, resp, nil
}
// getResponsemanagementLibraryIdByNameFn is an implementation of the function to get a Genesys Cloud responsemanagement library by name
func getResponsemanagementLibraryIdByNameFn(ctx context.Context, p *responsemanagementLibraryProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
librarys, resp, err := getAllResponsemanagementLibraryFn(ctx, p, name)
if err != nil {
return "", false, resp, err
}
if librarys == nil || len(*librarys) == 0 {
return "", true, resp, fmt.Errorf("No responsemanagement library found with name %s", name)
}
for _, library := range *librarys {
if *library.Name == name {
log.Printf("Retrieved the responsemanagement library id %s by name %s", *library.Id, name)
return *library.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find responsemanagement library with name %s", name)
}
// getResponsemanagementLibraryByIdFn is an implementation of the function to get a Genesys Cloud responsemanagement library by Id
func getResponsemanagementLibraryByIdFn(ctx context.Context, p *responsemanagementLibraryProxy, id string) (responsemanagementLibrary *platformclientv2.Library, response *platformclientv2.APIResponse, err error) {
library, resp, err := p.responseManagementApi.GetResponsemanagementLibrary(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve responsemanagement library by id %s: %s", id, err)
}
return library, resp, nil
}
// updateResponsemanagementLibraryFn is an implementation of the function to update a Genesys Cloud responsemanagement library
func updateResponsemanagementLibraryFn(ctx context.Context, p *responsemanagementLibraryProxy, id string, responsemanagementLibrary *platformclientv2.Library) (*platformclientv2.Library, *platformclientv2.APIResponse, error) {
lib, resp, err := getResponsemanagementLibraryByIdFn(ctx, p, id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update responsemanagement library: %s", err)
}
responsemanagementLibrary.Version = lib.Version
library, resp, err := p.responseManagementApi.PutResponsemanagementLibrary(id, *responsemanagementLibrary)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update responsemanagement library: %s", err)
}
return library, resp, nil
}
// deleteResponsemanagementLibraryFn is an implementation function for deleting a Genesys Cloud responsemanagement library
func deleteResponsemanagementLibraryFn(ctx context.Context, p *responsemanagementLibraryProxy, id string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.responseManagementApi.DeleteResponsemanagementLibrary(id)
if err != nil {
return resp, fmt.Errorf("Failed to delete responsemanagement library: %s", err)
}
return resp, nil
}
package responsemanagement_library
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_responsemanagement_library.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthResponsemanagementLibrary retrieves all of the responsemanagement library via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthResponsemanagementLibrarys(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := newResponsemanagementLibraryProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
librarys, resp, err := proxy.getAllResponsemanagementLibrary(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get responsemanagement library error: %s", err), resp)
}
for _, library := range *librarys {
resources[*library.Id] = &resourceExporter.ResourceMeta{Name: *library.Name}
}
return resources, nil
}
// createResponsemanagementLibrary is used by the responsemanagement_library resource to create Genesys cloud responsemanagement library
func createResponsemanagementLibrary(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementLibraryProxy(sdkConfig)
responsemanagementLibrary := platformclientv2.Library{
Name: platformclientv2.String(d.Get("name").(string)),
}
log.Printf("Creating responsemanagement library %s", *responsemanagementLibrary.Name)
library, resp, err := proxy.createResponsemanagementLibrary(ctx, &responsemanagementLibrary)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create responsemanagement library %s error: %s", *responsemanagementLibrary.Name, err), resp)
}
d.SetId(*library.Id)
log.Printf("Created responsemanagement library %s", *library.Id)
return readResponsemanagementLibrary(ctx, d, meta)
}
// readResponsemanagementLibrary is used by the responsemanagement_library resource to read an responsemanagement library from genesys cloud
func readResponsemanagementLibrary(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementLibraryProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceResponsemanagementLibrary(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading responsemanagement library %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
library, resp, getErr := proxy.getResponsemanagementLibraryById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read responsemanagement library %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read responsemanagement library %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", library.Name)
log.Printf("Read responsemanagement library %s %s", d.Id(), *library.Name)
return cc.CheckState(d)
})
}
// updateResponsemanagementLibrary is used by the responsemanagement_library resource to update an responsemanagement library in Genesys Cloud
func updateResponsemanagementLibrary(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementLibraryProxy(sdkConfig)
responsemanagementLibrary := platformclientv2.Library{
Name: platformclientv2.String(d.Get("name").(string)),
}
log.Printf("Updating responsemanagement library %s", *responsemanagementLibrary.Name)
library, resp, err := proxy.updateResponsemanagementLibrary(ctx, d.Id(), &responsemanagementLibrary)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update responsemanagement library %s error: %s", *responsemanagementLibrary.Name, err), resp)
}
log.Printf("Updated responsemanagement library %s", *library.Id)
return readResponsemanagementLibrary(ctx, d, meta)
}
// deleteResponsemanagementLibrary is used by the responsemanagement_library resource to delete an responsemanagement library from Genesys cloud
func deleteResponsemanagementLibrary(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementLibraryProxy(sdkConfig)
resp, err := proxy.deleteResponsemanagementLibrary(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete responsemanagement library %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getResponsemanagementLibraryById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted responsemanagement library %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting responsemanagement library %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("responsemanagement library %s still exists", d.Id()), resp))
})
}
package responsemanagement_library
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_responsemanagement_library_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the responsemanagement_library resource.
3. The datasource schema definitions for the responsemanagement_library datasource.
4. The resource exporter configuration for the responsemanagement_library exporter.
*/
const resourceName = "genesyscloud_responsemanagement_library"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceResponsemanagementLibrary())
regInstance.RegisterDataSource(resourceName, DataSourceResponsemanagementLibrary())
regInstance.RegisterExporter(resourceName, ResponsemanagementLibraryExporter())
}
// ResourceResponsemanagementLibrary registers the genesyscloud_responsemanagement_library resource with Terraform
func ResourceResponsemanagementLibrary() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud responsemanagement library`,
CreateContext: provider.CreateWithPooledClient(createResponsemanagementLibrary),
ReadContext: provider.ReadWithPooledClient(readResponsemanagementLibrary),
UpdateContext: provider.UpdateWithPooledClient(updateResponsemanagementLibrary),
DeleteContext: provider.DeleteWithPooledClient(deleteResponsemanagementLibrary),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The library name.`,
Required: true,
Type: schema.TypeString,
},
},
}
}
// ResponsemanagementLibraryExporter returns the resourceExporter object used to hold the genesyscloud_responsemanagement_library exporter's config
func ResponsemanagementLibraryExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthResponsemanagementLibrarys),
}
}
// DataSourceResponsemanagementLibrary registers the genesyscloud_responsemanagement_library data source
func DataSourceResponsemanagementLibrary() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Responsemanagement Library. Select a Responsemanagement Library by name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceResponsemanagementLibraryRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Responsemanagement Library name.`,
Type: schema.TypeString,
Required: true,
},
},
}
}
func GenerateResponseManagementLibraryResource(
resourceId string,
name string) string {
return fmt.Sprintf(`
resource "genesyscloud_responsemanagement_library" "%s" {
name = "%s"
}
`, resourceId, name)
}
package responsemanagement_response
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The data_source_genesyscloud_responsemanagement_response.go contains the data source implementation
for the resource.
*/
// dataSourceResponsemanagementResponseRead retrieves by name the id in question
func dataSourceResponsemanagementResponseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newResponsemanagementResponseProxy(sdkConfig)
name := d.Get("name").(string)
library := d.Get("library_id").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
managementResponseId, retryable, resp, err := proxy.getResponsemanagementResponseIdByName(ctx, name, library)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting responsemanagement response %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no responsemanagement response found with name %s | error: %s", name, err), resp))
}
d.SetId(managementResponseId)
return nil
})
}
package responsemanagement_response
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
)
/*
The genesyscloud_responsemanagement_response_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *responsemanagementResponseProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createResponsemanagementResponseFunc func(ctx context.Context, p *responsemanagementResponseProxy, response *platformclientv2.Response) (responseManagementResponse *platformclientv2.Response, resp *platformclientv2.APIResponse, err error)
type getAllResponsemanagementResponseFunc func(ctx context.Context, p *responsemanagementResponseProxy, libraryId string) (*[]platformclientv2.Response, *platformclientv2.APIResponse, error)
type getResponsemanagementResponseIdByNameFunc func(ctx context.Context, p *responsemanagementResponseProxy, name string, libraryId string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
type getResponsemanagementResponseByIdFunc func(ctx context.Context, p *responsemanagementResponseProxy, id string) (response *platformclientv2.Response, resp *platformclientv2.APIResponse, err error)
type updateResponsemanagementResponseFunc func(ctx context.Context, p *responsemanagementResponseProxy, id string, response *platformclientv2.Response) (*platformclientv2.Response, *platformclientv2.APIResponse, error)
type deleteResponsemanagementResponseFunc func(ctx context.Context, p *responsemanagementResponseProxy, id string) (resp *platformclientv2.APIResponse, err error)
// responsemanagementResponseProxy contains all of the methods that call genesys cloud APIs.
type responsemanagementResponseProxy struct {
clientConfig *platformclientv2.Configuration
responseManagementApi *platformclientv2.ResponseManagementApi
createResponsemanagementResponseAttr createResponsemanagementResponseFunc
getAllResponsemanagementResponseAttr getAllResponsemanagementResponseFunc
getResponsemanagementResponseIdByNameAttr getResponsemanagementResponseIdByNameFunc
getResponsemanagementResponseByIdAttr getResponsemanagementResponseByIdFunc
updateResponsemanagementResponseAttr updateResponsemanagementResponseFunc
deleteResponsemanagementResponseAttr deleteResponsemanagementResponseFunc
}
// newResponsemanagementResponseProxy initializes the responsemanagement response proxy with all of the data needed to communicate with Genesys Cloud
func newResponsemanagementResponseProxy(clientConfig *platformclientv2.Configuration) *responsemanagementResponseProxy {
api := platformclientv2.NewResponseManagementApiWithConfig(clientConfig)
return &responsemanagementResponseProxy{
clientConfig: clientConfig,
responseManagementApi: api,
createResponsemanagementResponseAttr: createResponsemanagementResponseFn,
getAllResponsemanagementResponseAttr: getAllResponsemanagementResponseFn,
getResponsemanagementResponseIdByNameAttr: getResponsemanagementResponseIdByNameFn,
getResponsemanagementResponseByIdAttr: getResponsemanagementResponseByIdFn,
updateResponsemanagementResponseAttr: updateResponsemanagementResponseFn,
deleteResponsemanagementResponseAttr: deleteResponsemanagementResponseFn,
}
}
// getResponsemanagementResponseProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getResponsemanagementResponseProxy(clientConfig *platformclientv2.Configuration) *responsemanagementResponseProxy {
if internalProxy == nil {
internalProxy = newResponsemanagementResponseProxy(clientConfig)
}
return internalProxy
}
// createResponsemanagementResponse creates a Genesys Cloud responsemanagement response
func (p *responsemanagementResponseProxy) createResponsemanagementResponse(ctx context.Context, responsemanagementResponse *platformclientv2.Response) (response *platformclientv2.Response, resp *platformclientv2.APIResponse, err error) {
return p.createResponsemanagementResponseAttr(ctx, p, responsemanagementResponse)
}
// getResponsemanagementResponse retrieves all Genesys Cloud responsemanagement response
func (p *responsemanagementResponseProxy) getAllResponsemanagementResponse(ctx context.Context) (*[]platformclientv2.Response, *platformclientv2.APIResponse, error) {
return p.getAllResponsemanagementResponseAttr(ctx, p, "")
}
// getResponsemanagementResponseIdByName returns a single Genesys Cloud responsemanagement response by a name
func (p *responsemanagementResponseProxy) getResponsemanagementResponseIdByName(ctx context.Context, name string, libraryId string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getResponsemanagementResponseIdByNameAttr(ctx, p, name, libraryId)
}
// getResponsemanagementResponseById returns a single Genesys Cloud responsemanagement response by Id
func (p *responsemanagementResponseProxy) getResponsemanagementResponseById(ctx context.Context, id string) (responsemanagementResponse *platformclientv2.Response, resp *platformclientv2.APIResponse, err error) {
return p.getResponsemanagementResponseByIdAttr(ctx, p, id)
}
// updateResponsemanagementResponse updates a Genesys Cloud responsemanagement response
func (p *responsemanagementResponseProxy) updateResponsemanagementResponse(ctx context.Context, id string, responsemanagementResponse *platformclientv2.Response) (*platformclientv2.Response, *platformclientv2.APIResponse, error) {
return p.updateResponsemanagementResponseAttr(ctx, p, id, responsemanagementResponse)
}
// deleteResponsemanagementResponse deletes a Genesys Cloud responsemanagement response by Id
func (p *responsemanagementResponseProxy) deleteResponsemanagementResponse(ctx context.Context, id string) (resp *platformclientv2.APIResponse, err error) {
return p.deleteResponsemanagementResponseAttr(ctx, p, id)
}
// createResponsemanagementResponseFn is an implementation function for creating a Genesys Cloud responsemanagement response
func createResponsemanagementResponseFn(ctx context.Context, p *responsemanagementResponseProxy, responsemanagementResponse *platformclientv2.Response) (*platformclientv2.Response, *platformclientv2.APIResponse, error) {
response, resp, err := p.responseManagementApi.PostResponsemanagementResponses(*responsemanagementResponse, "")
if err != nil {
return nil, resp, err
}
return response, resp, nil
}
// getAllResponsemanagementResponseFn is the implementation for retrieving all responsemanagement response in Genesys Cloud
func getAllResponsemanagementResponseFn(ctx context.Context, p *responsemanagementResponseProxy, libraryId string) (*[]platformclientv2.Response, *platformclientv2.APIResponse, error) {
var allResponseManagementResponses []platformclientv2.Response
const pageSize = 100
if libraryId != "" {
responses, resp, getErr := p.responseManagementApi.GetResponsemanagementResponses(libraryId, 1, pageSize, "")
if getErr != nil {
return nil, resp, fmt.Errorf("Error requesting page of Responsemanagement Response: %s", getErr)
}
if responses.Entities == nil || len(*responses.Entities) == 0 {
return &allResponseManagementResponses, resp, nil
}
for _, response := range *responses.Entities {
allResponseManagementResponses = append(allResponseManagementResponses, response)
}
return &allResponseManagementResponses, resp, nil
}
libraries, resp, getErr := p.responseManagementApi.GetResponsemanagementLibraries(1, pageSize, "", "")
if getErr != nil {
return nil, resp, fmt.Errorf("Error requesting page of Responsemanagement library: %s", getErr)
}
if libraries.Entities == nil || len(*libraries.Entities) == 0 {
return &allResponseManagementResponses, resp, nil
}
for _, library := range *libraries.Entities {
for pageNum := 1; ; pageNum++ {
responses, resp, getErr := p.responseManagementApi.GetResponsemanagementResponses(*library.Id, pageNum, pageSize, "")
if getErr != nil {
return nil, resp, fmt.Errorf("Error requesting page of Responsemanagement Response: %s", getErr)
}
if responses.Entities == nil || len(*responses.Entities) == 0 {
break
}
for _, response := range *responses.Entities {
allResponseManagementResponses = append(allResponseManagementResponses, response)
}
}
}
for pageNum := 2; pageNum <= *libraries.PageCount; pageNum++ {
libraries, resp, getErr := p.responseManagementApi.GetResponsemanagementLibraries(pageNum, pageSize, "", "")
if getErr != nil {
return nil, resp, fmt.Errorf("Error requesting page of Responsemanagement library: %s", getErr)
}
if libraries.Entities == nil || len(*libraries.Entities) == 0 {
break
}
for _, library := range *libraries.Entities {
for pageNum := 1; ; pageNum++ {
responses, resp, getErr := p.responseManagementApi.GetResponsemanagementResponses(*library.Id, pageNum, pageSize, "")
if getErr != nil {
return nil, resp, fmt.Errorf("Error requesting page of Responsemanagement Response: %s", getErr)
}
if responses.Entities == nil || len(*responses.Entities) == 0 {
break
}
for _, response := range *responses.Entities {
allResponseManagementResponses = append(allResponseManagementResponses, response)
}
}
}
}
return &allResponseManagementResponses, resp, nil
}
// getResponsemanagementResponseIdByNameFn is an implementation of the function to get a Genesys Cloud responsemanagement response by name
func getResponsemanagementResponseIdByNameFn(ctx context.Context, p *responsemanagementResponseProxy, name string, libraryId string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
responses, resp, err := getAllResponsemanagementResponseFn(ctx, p, libraryId)
if err != nil {
return "", false, resp, fmt.Errorf("Error searching response management responses %s: %s", name, err)
}
var response platformclientv2.Response
for _, responseSdk := range *responses {
if *responseSdk.Name == name {
log.Printf("Retrieved the response management response %s by name %s", *responseSdk.Id, name)
response = responseSdk
return *response.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find response management response with name %s", name)
}
// getResponsemanagementResponseByIdFn is an implementation of the function to get a Genesys Cloud responsemanagement response by Id
func getResponsemanagementResponseByIdFn(ctx context.Context, p *responsemanagementResponseProxy, id string) (responsemanagementResponse *platformclientv2.Response, resp *platformclientv2.APIResponse, err error) {
mamangementResponse, resp, err := p.responseManagementApi.GetResponsemanagementResponse(id, "")
if err != nil {
return nil, resp, err
}
return mamangementResponse, resp, nil
}
// updateResponsemanagementResponseFn is an implementation of the function to update a Genesys Cloud responsemanagement response
func updateResponsemanagementResponseFn(ctx context.Context, p *responsemanagementResponseProxy, id string, response *platformclientv2.Response) (*platformclientv2.Response, *platformclientv2.APIResponse, error) {
responsemanagementResponse, resp, err := p.responseManagementApi.GetResponsemanagementResponse(id, "")
if err != nil {
return nil, resp, err
}
response.Version = responsemanagementResponse.Version
responsemanagementResponse, resp, updateErr := p.responseManagementApi.PutResponsemanagementResponse(id, *response, "")
if updateErr != nil {
return nil, resp, updateErr
}
return responsemanagementResponse, resp, nil
}
// deleteResponsemanagementResponseFn is an implementation function for deleting a Genesys Cloud responsemanagement response
func deleteResponsemanagementResponseFn(ctx context.Context, p *responsemanagementResponseProxy, id string) (*platformclientv2.APIResponse, error) {
resp, err := p.responseManagementApi.DeleteResponsemanagementResponse(id)
if err != nil {
return resp, err
}
return resp, err
}
package responsemanagement_response
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
)
/*
The resource_genesyscloud_responsemanagement_response.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthResponsemanagementResponse retrieves all of the responsemanagement response via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthResponsemanagementResponses(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getResponsemanagementResponseProxy(clientConfig)
responseManagementResponses, resp, err := proxy.getAllResponsemanagementResponse(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get list of response management responses error: %s", err), resp)
}
for _, response := range *responseManagementResponses {
resources[*response.Id] = &resourceExporter.ResourceMeta{Name: *response.Name}
}
return resources, nil
}
// createResponsemanagementResponse is used by the responsemanagement_response resource to create Genesys cloud responsemanagement response
func createResponsemanagementResponse(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementResponseProxy(sdkConfig)
sdkResponse := getResponseFromResourceData(d)
diagErr := util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
log.Printf("Creating Responsemanagement Response %s", *sdkResponse.Name)
responsemanagementResponse, resp, err := proxy.createResponsemanagementResponse(ctx, &sdkResponse)
if err != nil {
if util.IsStatus412(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to create Responsemanagement Response %s | error: %s", *sdkResponse.Name, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to create Responsemanagement Response %s | error: %s", *sdkResponse.Name, err), resp))
}
d.SetId(*responsemanagementResponse.Id)
log.Printf("Created Responsemanagement Response %s %s", *sdkResponse.Name, *responsemanagementResponse.Id)
return nil
})
if diagErr != nil {
return diagErr
}
return readResponsemanagementResponse(ctx, d, meta)
}
// readResponsemanagementResponse is used by the responsemanagement_response resource to read a responsemanagement response from genesys cloud
func readResponsemanagementResponse(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementResponseProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceResponsemanagementResponse(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Responsemanagement Response %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkResponse, resp, getErr := proxy.getResponsemanagementResponseById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Responsemanagement Response %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Responsemanagement Response %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", sdkResponse.Name)
if sdkResponse.Libraries != nil {
d.Set("library_ids", util.SdkDomainEntityRefArrToList(*sdkResponse.Libraries))
}
resourcedata.SetNillableValueWithSchemaSetWithFunc(d, "texts", sdkResponse.Texts, flattenResponseTexts)
resourcedata.SetNillableValueWithSchemaSetWithFunc(d, "substitutions", sdkResponse.Substitutions, flattenResponseSubstitutions)
resourcedata.SetNillableValue(d, "interaction_type", sdkResponse.InteractionType)
if sdkResponse.SubstitutionsSchema != nil && sdkResponse.SubstitutionsSchema.Id != nil {
d.Set("substitutions_schema_id", *sdkResponse.SubstitutionsSchema.Id)
}
if sdkResponse.ResponseType != nil {
d.Set("response_type", *sdkResponse.ResponseType)
}
if sdkResponse.MessagingTemplate != nil {
d.Set("messaging_template", flattenMessagingTemplate(sdkResponse.MessagingTemplate))
}
resourcedata.SetNillableValueWithSchemaSetWithFunc(d, "asset_ids", sdkResponse.Assets, flattenAddressableEntityRefs)
resourcedata.SetNillableValueWithSchemaSetWithFunc(d, "footer", sdkResponse.Footer, flattenFooterTemplate)
log.Printf("Read Responsemanagement Response %s %s", d.Id(), *sdkResponse.Name)
return cc.CheckState(d)
})
}
// updateResponsemanagementResponse is used by the responsemanagement_response resource to update an responsemanagement response in Genesys Cloud
func updateResponsemanagementResponse(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementResponseProxy(sdkConfig)
sdkResponse := getResponseFromResourceData(d)
log.Printf("Updating Responsemanagement Response %s", *sdkResponse.Name)
managementResponse, resp, err := proxy.updateResponsemanagementResponse(ctx, d.Id(), &sdkResponse)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update response management response %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated Responsemanagement Response %s", *managementResponse.Id)
return readResponsemanagementResponse(ctx, d, meta)
}
// deleteResponsemanagementResponse is used by the responsemanagement_response resource to delete an responsemanagement response from Genesys cloud
func deleteResponsemanagementResponse(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getResponsemanagementResponseProxy(sdkConfig)
log.Printf("Deleting Responsemanagement Response")
resp, err := proxy.deleteResponsemanagementResponse(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Responsemanagement Response %s error: %s", d.Id(), err), resp)
}
time.Sleep(30 * time.Second) //Give time for any libraries or assets to be deleted
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getResponsemanagementResponseById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Responsemanagement Response deleted
log.Printf("Deleted Responsemanagement Response %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Responsemanagement Response %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Responsemanagement Response %s still exists", d.Id()), resp))
})
}
package responsemanagement_response
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_responsemanagement_response_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the responsemanagement_response resource.
3. The datasource schema definitions for the responsemanagement_response datasource.
4. The resource exporter configuration for the responsemanagement_response exporter.
*/
const resourceName = "genesyscloud_responsemanagement_response"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceResponsemanagementResponse())
regInstance.RegisterDataSource(resourceName, DataSourceResponsemanagementResponse())
regInstance.RegisterExporter(resourceName, ResponsemanagementResponseExporter())
}
var (
responsetextResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`content`: {
Description: `Response text content.`,
Required: true,
Type: schema.TypeString,
},
`content_type`: {
Description: `Response text content type.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`text/plain`, `text/html`}, false),
},
},
}
footerResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`type`: {
Description: `Specifies the type represented by Footer.Valid values: Signature.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`Signature`}, false),
},
`applicable_resources`: {
Description: `Specifies the canned response template where the footer can be used.Valid values: Campaign.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
substitutionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`id`: {
Description: `Response substitution identifier.`,
Required: true,
Type: schema.TypeString,
},
`description`: {
Description: `Response substitution description.`,
Optional: true,
Type: schema.TypeString,
},
`default_value`: {
Description: `Response substitution default value.`,
Optional: true,
Type: schema.TypeString,
},
},
}
messagingtemplateResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`whats_app`: {
Description: `Defines a messaging template for a WhatsApp messaging channel`,
Optional: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: whatsappDefinitionResource,
Set: func(_ interface{}) int {
return 0
},
},
},
}
whatsappDefinitionResource = &schema.Resource{
Schema: map[string]*schema.Schema{
`name`: {
Description: `The messaging template name.`,
Required: true,
Type: schema.TypeString,
},
`namespace`: {
Description: `The messaging template namespace.`,
Required: true,
Type: schema.TypeString,
},
`language`: {
Description: `The messaging template language configured for this template. This is a WhatsApp specific value. For example, 'en_US'`,
Required: true,
Type: schema.TypeString,
},
},
}
)
// ResourceResponsemanagementResponse registers the genesyscloud_responsemanagement_response resource with Terraform
func ResourceResponsemanagementResponse() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud responsemanagement response`,
CreateContext: provider.CreateWithPooledClient(createResponsemanagementResponse),
ReadContext: provider.ReadWithPooledClient(readResponsemanagementResponse),
UpdateContext: provider.UpdateWithPooledClient(updateResponsemanagementResponse),
DeleteContext: provider.DeleteWithPooledClient(deleteResponsemanagementResponse),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `Name of the responsemanagement response`,
Required: true,
Type: schema.TypeString,
},
`library_ids`: {
Description: `One or more libraries response is associated with. Changing the library IDs will result in the resource being recreated`,
Required: true,
ForceNew: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`texts`: {
Description: `One or more texts associated with the response.`,
Required: true,
Type: schema.TypeSet,
Elem: responsetextResource,
},
`interaction_type`: {
Description: `The interaction type for this response.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`chat`, `email`, `twitter`}, false),
},
`substitutions`: {
Description: `Details about any text substitutions used in the texts for this response.`,
Optional: true,
Type: schema.TypeSet,
Elem: substitutionResource,
},
`substitutions_schema_id`: {
Description: `Metadata about the text substitutions in json schema format.`,
Optional: true,
Type: schema.TypeString,
},
`response_type`: {
Description: `The response type represented by the response.`,
Optional: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{`MessagingTemplate`, `CampaignSmsTemplate`, `CampaignEmailTemplate`, `Footer`}, false),
},
`messaging_template`: {
Description: `An optional messaging template definition for responseType.MessagingTemplate.`,
Optional: true,
MaxItems: 1,
Type: schema.TypeSet,
Elem: messagingtemplateResource,
Set: func(_ interface{}) int {
return 0
},
},
`asset_ids`: {
Description: `Assets used in the response`,
Optional: true,
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
},
`footer`: {
Description: `Footer template identifies the Footer type and its footerUsage`,
Optional: true,
Type: schema.TypeSet,
MaxItems: 1,
Elem: footerResource,
},
},
}
}
// ResponsemanagementResponseExporter returns the resourceExporter object used to hold the genesyscloud_responsemanagement_response exporter's config
func ResponsemanagementResponseExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthResponsemanagementResponses),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
`library_ids`: {
RefType: "genesyscloud_responsemanagement_library",
},
`asset_ids`: {
RefType: "responsemanagement_responseasset",
},
},
JsonEncodeAttributes: []string{"substitutions_schema_id"},
}
}
// DataSourceResponsemanagementResponse registers the genesyscloud_responsemanagement_response data source
func DataSourceResponsemanagementResponse() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Responsemanagement Response. Select a Responsemanagement Response by name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceResponsemanagementResponseRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Responsemanagement Response name.`,
Type: schema.TypeString,
Required: true,
},
"library_id": {
Description: `ID of the library that contains the response.`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package responsemanagement_response
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
func getResponseFromResourceData(d *schema.ResourceData) platformclientv2.Response {
interactionType := d.Get("interaction_type").(string)
substitutionsSchema := d.Get("substitutions_schema_id").(string)
responseType := d.Get("response_type").(string)
messagingTemplate := d.Get("messaging_template").(*schema.Set)
response := platformclientv2.Response{
Name: platformclientv2.String(d.Get("name").(string)),
Libraries: util.BuildSdkDomainEntityRefArr(d, "library_ids"),
Texts: buildResponseTexts(d.Get("texts").(*schema.Set)),
Substitutions: buildResponseSubstitutions(d.Get("substitutions").(*schema.Set)),
Assets: buildAddressableEntityRefs(d.Get("asset_ids").(*schema.Set)),
Footer: buildFooterTemplate(d.Get("footer").(*schema.Set)),
}
if interactionType != "" {
response.InteractionType = &interactionType
}
if substitutionsSchema != "" {
response.SubstitutionsSchema = &platformclientv2.Jsonschemadocument{Id: &substitutionsSchema}
}
if responseType != "" {
response.ResponseType = &responseType
}
// Need to check messaging template like this to avoid the responseType being giving a default value
if messagingTemplate.Len() > 0 {
response.MessagingTemplate = buildMessagingTemplate(messagingTemplate)
}
return response
}
func buildResponseTexts(responseTexts *schema.Set) *[]platformclientv2.Responsetext {
if responseTexts == nil {
return nil
}
sdkResponseTexts := make([]platformclientv2.Responsetext, 0)
responseTextList := responseTexts.List()
for _, responseText := range responseTextList {
var sdkResponseText platformclientv2.Responsetext
responseTextMap := responseText.(map[string]interface{})
resourcedata.BuildSDKStringValueIfNotNil(&sdkResponseText.Content, responseTextMap, "content")
resourcedata.BuildSDKStringValueIfNotNil(&sdkResponseText.ContentType, responseTextMap, "content_type")
sdkResponseTexts = append(sdkResponseTexts, sdkResponseText)
}
return &sdkResponseTexts
}
func buildResponseSubstitutions(responseSubstitutions *schema.Set) *[]platformclientv2.Responsesubstitution {
if responseSubstitutions == nil {
return nil
}
sdkResponseSubstitutions := make([]platformclientv2.Responsesubstitution, 0)
responseSubstitutionList := responseSubstitutions.List()
for _, responseSubstitution := range responseSubstitutionList {
var sdkResponseSubstitution platformclientv2.Responsesubstitution
responseSubstitutionMap := responseSubstitution.(map[string]interface{})
sdkResponseSubstitution.Id = platformclientv2.String(responseSubstitutionMap["id"].(string))
resourcedata.BuildSDKStringValueIfNotNil(&sdkResponseSubstitution.Description, responseSubstitutionMap, "description")
resourcedata.BuildSDKStringValueIfNotNil(&sdkResponseSubstitution.DefaultValue, responseSubstitutionMap, "default_value")
sdkResponseSubstitutions = append(sdkResponseSubstitutions, sdkResponseSubstitution)
}
return &sdkResponseSubstitutions
}
func buildWhatsappDefinition(whatsappDefinition *schema.Set) *platformclientv2.Whatsappdefinition {
if whatsappDefinition == nil {
return nil
}
var sdkWhatsappDefinition platformclientv2.Whatsappdefinition
whatsappDefinitionList := whatsappDefinition.List()
if len(whatsappDefinitionList) > 0 {
whatsappDefinitionMap := whatsappDefinitionList[0].(map[string]interface{})
resourcedata.BuildSDKStringValueIfNotNil(&sdkWhatsappDefinition.Name, whatsappDefinitionMap, "name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkWhatsappDefinition.Namespace, whatsappDefinitionMap, "namespace")
resourcedata.BuildSDKStringValueIfNotNil(&sdkWhatsappDefinition.Language, whatsappDefinitionMap, "language")
}
return &sdkWhatsappDefinition
}
func buildFooterTemplate(footerTemplate *schema.Set) *platformclientv2.Footertemplate {
if footerTemplate == nil {
return nil
}
footerTemplateList := footerTemplate.List()
var sdkFooterTemplate platformclientv2.Footertemplate
if len(footerTemplateList) > 0 {
footerTemplateMap := footerTemplateList[0].(map[string]interface{})
resourcedata.BuildSDKStringValueIfNotNil(&sdkFooterTemplate.VarType, footerTemplateMap, "type")
if applicableResources, exists := footerTemplateMap["applicable_resources"].([]interface{}); exists {
applicableResourcesList := lists.InterfaceListToStrings(applicableResources)
sdkFooterTemplate.ApplicableResources = &applicableResourcesList
}
}
return &sdkFooterTemplate
}
func buildMessagingTemplate(messagingTemplate *schema.Set) *platformclientv2.Messagingtemplate {
if messagingTemplate == nil {
return nil
}
var sdkMessagingTemplate platformclientv2.Messagingtemplate
messagingTemplateList := messagingTemplate.List()
if len(messagingTemplateList) > 0 {
messagingTemplateMap := messagingTemplateList[0].(map[string]interface{})
if whatsApp := messagingTemplateMap["whats_app"]; whatsApp != nil {
sdkMessagingTemplate.WhatsApp = buildWhatsappDefinition(whatsApp.(*schema.Set))
}
}
return &sdkMessagingTemplate
}
func buildAddressableEntityRefs(addressableEntityRef *schema.Set) *[]platformclientv2.Addressableentityref {
if addressableEntityRef == nil {
return nil
}
strList := lists.SetToStringList(addressableEntityRef)
if strList == nil {
return nil
}
addressableEntityRefs := make([]platformclientv2.Addressableentityref, len(*strList))
for i, id := range *strList {
tempId := id
addressableEntityRefs[i] = platformclientv2.Addressableentityref{Id: &tempId}
}
return &addressableEntityRefs
}
func flattenResponseTexts(responseTexts *[]platformclientv2.Responsetext) *schema.Set {
if len(*responseTexts) == 0 {
return nil
}
responseTextSet := schema.NewSet(schema.HashResource(responsetextResource), []interface{}{})
for _, responseText := range *responseTexts {
responseTextMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(responseTextMap, "content", responseText.Content)
resourcedata.SetMapValueIfNotNil(responseTextMap, "content_type", responseText.ContentType)
responseTextSet.Add(responseTextMap)
}
return responseTextSet
}
func flattenResponseSubstitutions(responseSubstitutions *[]platformclientv2.Responsesubstitution) *schema.Set {
if len(*responseSubstitutions) == 0 {
return nil
}
responseSubstitutionSet := schema.NewSet(schema.HashResource(substitutionResource), []interface{}{})
for _, responseSubstitution := range *responseSubstitutions {
responseSubstitutionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(responseSubstitutionMap, "id", responseSubstitution.Id)
resourcedata.SetMapValueIfNotNil(responseSubstitutionMap, "description", responseSubstitution.Description)
resourcedata.SetMapValueIfNotNil(responseSubstitutionMap, "default_value", responseSubstitution.DefaultValue)
responseSubstitutionSet.Add(responseSubstitutionMap)
}
return responseSubstitutionSet
}
func flattenWhatsappDefinition(whatsappDefinition *platformclientv2.Whatsappdefinition) *schema.Set {
if whatsappDefinition == nil {
return nil
}
whatsappDefinitionSet := schema.NewSet(schema.HashResource(whatsappDefinitionResource), []interface{}{})
whatsappDefinitionMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(whatsappDefinitionMap, "name", whatsappDefinition.Name)
resourcedata.SetMapValueIfNotNil(whatsappDefinitionMap, "namespace", whatsappDefinition.Namespace)
resourcedata.SetMapValueIfNotNil(whatsappDefinitionMap, "language", whatsappDefinition.Language)
whatsappDefinitionSet.Add(whatsappDefinitionMap)
return whatsappDefinitionSet
}
func flattenFooterTemplate(footerTemplate *platformclientv2.Footertemplate) *schema.Set {
if footerTemplate == nil {
return nil
}
footerTemplateSet := schema.NewSet(schema.HashResource(footerResource), []interface{}{})
footerTemplateMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(footerTemplateMap, "type", footerTemplate.VarType)
if footerTemplate.ApplicableResources != nil {
footerTemplateMap["applicable_resources"] = lists.StringListToInterfaceList(*footerTemplate.ApplicableResources)
}
footerTemplateSet.Add(footerTemplateMap)
return footerTemplateSet
}
func flattenMessagingTemplate(messagingTemplate *platformclientv2.Messagingtemplate) *schema.Set {
if messagingTemplate == nil {
return nil
}
messagingTemplateSet := schema.NewSet(schema.HashResource(messagingtemplateResource), []interface{}{})
messagingTemplateMap := make(map[string]interface{})
if messagingTemplate.WhatsApp != nil {
messagingTemplateMap["whats_app"] = flattenWhatsappDefinition(messagingTemplate.WhatsApp)
}
messagingTemplateSet.Add(messagingTemplateMap)
return messagingTemplateSet
}
func flattenAddressableEntityRefs(addressableEntityRefs *[]platformclientv2.Addressableentityref) *schema.Set {
addressableEntityRefList := make([]interface{}, len(*addressableEntityRefs))
for i, v := range *addressableEntityRefs {
addressableEntityRefList[i] = *v.Id
}
return schema.NewSet(schema.HashString, addressableEntityRefList)
}
package responsemanagement_responseasset
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
)
func dataSourceResponseManagementResponseAssetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
proxy := getRespManagementRespAssetProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
responseId, retryable, resp, err := proxy.getRespManagementRespAssetByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching responsemanagement response asset %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No responsemanagement response asset found with name %s", name), resp))
}
d.SetId(responseId)
return nil
})
}
package responsemanagement_responseasset
import (
"context"
"fmt"
"log"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_responsemanagement_responseasset_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *responsemanagementResponseassetProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllResponseAssetsFunc func(ctx context.Context, p *responsemanagementResponseassetProxy) (*[]platformclientv2.Responseasset, *platformclientv2.APIResponse, error)
type createRespManagementRespAssetFunc func(ctx context.Context, p *responsemanagementResponseassetProxy, respAsset *platformclientv2.Createresponseassetrequest) (*platformclientv2.Createresponseassetresponse, *platformclientv2.APIResponse, error)
type updateRespManagementRespAssetFunc func(ctx context.Context, p *responsemanagementResponseassetProxy, id string, respAsset *platformclientv2.Responseassetrequest) (*platformclientv2.Responseasset, *platformclientv2.APIResponse, error)
type getRespManagementRespAssetByIdFunc func(ctx context.Context, p *responsemanagementResponseassetProxy, id string) (*platformclientv2.Responseasset, *platformclientv2.APIResponse, error)
type getRespManagementRespAssetByNameFunc func(ctx context.Context, p *responsemanagementResponseassetProxy, name string) (string, bool, *platformclientv2.APIResponse, error)
type deleteRespManagementRespAssetFunc func(ctx context.Context, p *responsemanagementResponseassetProxy, id string) (response *platformclientv2.APIResponse, err error)
// responsemanagementResponseassetProxy contains all of the methods that call genesys cloud APIs.
type responsemanagementResponseassetProxy struct {
clientConfig *platformclientv2.Configuration
responseManagementApi *platformclientv2.ResponseManagementApi
getAllResponseAssetsAttr getAllResponseAssetsFunc
createRespManagementRespAssetAttr createRespManagementRespAssetFunc
updateRespManagementRespAssetAttr updateRespManagementRespAssetFunc
getRespManagementRespAssetByIdAttr getRespManagementRespAssetByIdFunc
getRespManagementRespAssetByNameAttr getRespManagementRespAssetByNameFunc
deleteRespManagementRespAssetAttr deleteRespManagementRespAssetFunc
assetCache rc.CacheInterface[platformclientv2.Responseasset]
}
// newRespManagementRespAssetProxy initializes the responsemanagement responseasset proxy with all of the data needed to communicate with Genesys Cloud
func newRespManagementRespAssetProxy(clientConfig *platformclientv2.Configuration) *responsemanagementResponseassetProxy {
api := platformclientv2.NewResponseManagementApiWithConfig(clientConfig)
assetCache := rc.NewResourceCache[platformclientv2.Responseasset]()
return &responsemanagementResponseassetProxy{
clientConfig: clientConfig,
responseManagementApi: api,
getAllResponseAssetsAttr: getAllResponseAssetsFn,
createRespManagementRespAssetAttr: createRespManagementRespAssetFn,
updateRespManagementRespAssetAttr: updateRespManagementRespAssetFn,
getRespManagementRespAssetByIdAttr: getRespManagementRespAssetByIdFn,
getRespManagementRespAssetByNameAttr: getRespManagementRespAssetByNameFn,
deleteRespManagementRespAssetAttr: deleteRespManagementRespAssetFn,
assetCache: assetCache,
}
}
// getRespManagementRespAssetProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getRespManagementRespAssetProxy(clientConfig *platformclientv2.Configuration) *responsemanagementResponseassetProxy {
if internalProxy == nil {
internalProxy = newRespManagementRespAssetProxy(clientConfig)
}
return internalProxy
}
func (p *responsemanagementResponseassetProxy) getAllResponseAssets(ctx context.Context) (*[]platformclientv2.Responseasset, *platformclientv2.APIResponse, error) {
return p.getAllResponseAssetsAttr(ctx, p)
}
// createRespManagementRespAsset creates a Genesys Cloud responsemanagement responseasset by Id
func (p *responsemanagementResponseassetProxy) createRespManagementRespAsset(ctx context.Context, respAsset *platformclientv2.Createresponseassetrequest) (*platformclientv2.Createresponseassetresponse, *platformclientv2.APIResponse, error) {
return p.createRespManagementRespAssetAttr(ctx, p, respAsset)
}
// updateRespManagementRespAsset updates a Genesys Cloud responsemanagement responseasset by Id
func (p *responsemanagementResponseassetProxy) updateRespManagementRespAsset(ctx context.Context, id string, respAsset *platformclientv2.Responseassetrequest) (*platformclientv2.Responseasset, *platformclientv2.APIResponse, error) {
return p.updateRespManagementRespAssetAttr(ctx, p, id, respAsset)
}
// getRespManagementRespAssetById returns a single Genesys Cloud responsemanagement responseasset by Id
func (p *responsemanagementResponseassetProxy) getRespManagementRespAssetById(ctx context.Context, id string) (*platformclientv2.Responseasset, *platformclientv2.APIResponse, error) {
return p.getRespManagementRespAssetByIdAttr(ctx, p, id)
}
func (p *responsemanagementResponseassetProxy) getRespManagementRespAssetByName(ctx context.Context, name string) (string, bool, *platformclientv2.APIResponse, error) {
return p.getRespManagementRespAssetByNameAttr(ctx, p, name)
}
// deleteRespManagementRespAsset deletes a Genesys Cloud responsemanagement responseasset by Id
func (p *responsemanagementResponseassetProxy) deleteRespManagementRespAsset(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteRespManagementRespAssetAttr(ctx, p, id)
}
func getAllResponseAssetsFn(ctx context.Context, p *responsemanagementResponseassetProxy) (*[]platformclientv2.Responseasset, *platformclientv2.APIResponse, error) {
var allResponseAssets []platformclientv2.Responseasset
var response *platformclientv2.APIResponse
pageSize := 100
responseAssets, resp, err := p.responseManagementApi.PostResponsemanagementResponseassetsSearch(platformclientv2.Responseassetsearchrequest{
PageSize: &pageSize,
PageNumber: platformclientv2.Int(1),
}, []string{})
response = resp
if err != nil {
return nil, resp, fmt.Errorf("Failed to get response asset search request: %v", err)
}
if responseAssets.Results == nil || len(*responseAssets.Results) == 0 {
return &allResponseAssets, resp, nil
}
allResponseAssets = append(allResponseAssets, *responseAssets.Results...)
for pageNum := 2; pageNum <= *responseAssets.PageCount; pageNum++ {
responseAssets, resp, err := p.responseManagementApi.PostResponsemanagementResponseassetsSearch(platformclientv2.Responseassetsearchrequest{
PageSize: &pageSize,
PageNumber: &pageNum,
}, []string{})
response = resp
if err != nil {
return nil, resp, fmt.Errorf("Failed to get response asset search request: %v", err)
}
if responseAssets.Results == nil || len(*responseAssets.Results) == 0 {
break
}
allResponseAssets = append(allResponseAssets, *responseAssets.Results...)
}
for _, asset := range allResponseAssets {
rc.SetCache(p.assetCache, *asset.Id, asset)
}
return &allResponseAssets, response, nil
}
// createRespManagementRespAssetFn is an implementation of the function to create a Genesys Cloud responsemanagement responseasset
func createRespManagementRespAssetFn(ctx context.Context, p *responsemanagementResponseassetProxy, respAsset *platformclientv2.Createresponseassetrequest) (*platformclientv2.Createresponseassetresponse, *platformclientv2.APIResponse, error) {
postResponseData, resp, err := p.responseManagementApi.PostResponsemanagementResponseassetsUploads(*respAsset)
if err != nil {
return nil, resp, fmt.Errorf("Failed to upload response asset: %v", err)
}
return postResponseData, resp, nil
}
// updateRespManagementRespAssetFn is an implementation of the function to update a Genesys Cloud responsemanagement responseasset
func updateRespManagementRespAssetFn(ctx context.Context, p *responsemanagementResponseassetProxy, id string, respAsset *platformclientv2.Responseassetrequest) (*platformclientv2.Responseasset, *platformclientv2.APIResponse, error) {
putResponseData, resp, err := p.responseManagementApi.PutResponsemanagementResponseasset(id, *respAsset)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update Responsemanagement response asset %s: %v", id, err)
}
return putResponseData, resp, nil
}
// getRespManagementRespAssetByIdFn is an implementation of the function to get a Genesys Cloud responsemanagement responseasset by Id
func getRespManagementRespAssetByIdFn(ctx context.Context, p *responsemanagementResponseassetProxy, id string) (*platformclientv2.Responseasset, *platformclientv2.APIResponse, error) {
asset := rc.GetCacheItem(p.assetCache, id)
if asset != nil {
return asset, nil, nil
}
sdkAsset, resp, getErr := p.responseManagementApi.GetResponsemanagementResponseasset(id)
if getErr != nil {
return nil, resp, fmt.Errorf("failed to retrieve response asset: %s", getErr)
}
return sdkAsset, resp, nil
}
func getRespManagementRespAssetByNameFn(ctx context.Context, p *responsemanagementResponseassetProxy, name string) (string, bool, *platformclientv2.APIResponse, error) {
var (
field = "name"
fields = []string{field}
varType = "TERM"
filter = platformclientv2.Responseassetfilter{
Fields: &fields,
Value: &name,
VarType: &varType,
}
body = platformclientv2.Responseassetsearchrequest{
Query: &[]platformclientv2.Responseassetfilter{filter},
SortBy: &field,
}
)
respAssets, resp, err := p.responseManagementApi.PostResponsemanagementResponseassetsSearch(body, nil)
if err != nil {
return "", false, resp, err
}
if respAssets == nil || len(*respAssets.Results) == 0 {
return "", true, resp, fmt.Errorf("No responsemanagement response asset found with name %s", name)
}
for _, asset := range *respAssets.Results {
if *asset.Name == name {
log.Printf("Retrieved the responsemanagement response asset id %s by name %s", *asset.Id, name)
return *asset.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find responsemanagement response asset with name %s", name)
}
// deleteRespManagementRespAssetFn is an implementation function for deleting a Genesys Cloud responsemanagement responseasset
func deleteRespManagementRespAssetFn(ctx context.Context, p *responsemanagementResponseassetProxy, id string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.responseManagementApi.DeleteResponsemanagementResponseasset(id)
if err != nil {
return resp, fmt.Errorf("failed to delete response asset: %s", err)
}
return resp, nil
}
package responsemanagement_responseasset
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/files"
"time"
)
/*
The resource_genesyscloud_responsemanagement_responseasset.go contains all the methods that perform the core logic for a resource.
*/
func getAllResponseAssets(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getRespManagementRespAssetProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
assets, resp, err := proxy.getAllResponseAssets(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get response management response assets | Error: %s", err), resp)
}
for _, asset := range *assets {
resources[*asset.Id] = &resourceExporter.ResourceMeta{Name: *asset.Name}
}
return resources, nil
}
// createResponsemanagementResponseasset is used by the responsemanagement_responseasset resource to create Genesys cloud responsemanagement responseasset
func createRespManagementRespAsset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
fileName := d.Get("filename").(string)
divisionId := d.Get("division_id").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRespManagementRespAssetProxy(sdkConfig)
sdkResponseAsset := platformclientv2.Createresponseassetrequest{}
if fileName != "" {
sdkResponseAsset.Name = &fileName
}
if divisionId != "" {
sdkResponseAsset.DivisionId = &divisionId
}
log.Printf("Creating Responsemanagement response asset %s", fileName)
postResponseData, resp, err := proxy.createRespManagementRespAsset(ctx, &sdkResponseAsset)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to upload response asset: %s | error: %s", fileName, err), resp)
}
headers := *postResponseData.Headers
url := *postResponseData.Url
reader, _, err := files.DownloadOrOpenFile(fileName)
if err != nil {
return diag.FromErr(err)
}
s3Uploader := files.NewS3Uploader(reader, nil, nil, headers, "PUT", url)
_, err = s3Uploader.Upload()
if err != nil {
return diag.FromErr(err)
}
d.SetId(*postResponseData.Id)
log.Printf("Created Responsemanagement response asset %s %s", fileName, *postResponseData.Id)
return readRespManagementRespAsset(ctx, d, meta)
}
// readResponsemanagementResponseasset is used by the responsemanagement_responseasset resource to read an responsemanagement responseasset from genesys cloud
func readRespManagementRespAsset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRespManagementRespAssetProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceResponseManagementResponseAsset(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Responsemanagement response asset %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkAsset, resp, getErr := proxy.getRespManagementRespAssetById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read response asset %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read response asset %s | error: %s", d.Id(), getErr), resp))
}
d.Set("filename", *sdkAsset.Name)
if sdkAsset.Division != nil && sdkAsset.Division.Id != nil {
d.Set("division_id", *sdkAsset.Division.Id)
}
log.Printf("Read Responsemanagement response asset %s %s", d.Id(), *sdkAsset.Name)
return cc.CheckState(d)
})
}
// updateResponsemanagementResponseasset is used by the responsemanagement_responseasset resource to update an responsemanagement responseasset in Genesys Cloud
func updateRespManagementRespAsset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRespManagementRespAssetProxy(sdkConfig)
fileName := d.Get("filename").(string)
divisionId := d.Get("division_id").(string)
var bodyRequest platformclientv2.Responseassetrequest
bodyRequest.Name = &fileName
if divisionId != "" {
bodyRequest.DivisionId = &divisionId
}
log.Printf("Updating Responsemanagement response asset %s", d.Id())
putResponseData, resp, err := proxy.updateRespManagementRespAsset(ctx, d.Id(), &bodyRequest)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to update response asset: %s | error: %s", d.Id(), err), resp)
}
// Adding a sleep with retry logic to determine when the division ID has actually been updated.
maxRetries := 5
for i := 0; i < maxRetries; i++ {
log.Printf("Reading response asset %s", d.Id())
time.Sleep(20 * time.Second)
getResponseData, resp, err := proxy.getRespManagementRespAssetById(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to read response asset: %s | error: %s", d.Id(), err), resp)
}
if *getResponseData.Division.Id == *putResponseData.Division.Id {
log.Printf("Updated Responsemanagement response asset %s", d.Id())
return readRespManagementRespAsset(ctx, d, meta)
}
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Responsemanagement response asset %s did not update properly | error: %s", d.Id(), err), resp)
}
// deleteResponsemanagementResponseasset is used by the responsemanagement_responseasset resource to delete an responsemanagement responseasset from Genesys cloud
func deleteRespManagementRespAsset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRespManagementRespAssetProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Responsemanagement response asset")
resp, err := proxy.deleteRespManagementRespAsset(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to delete response asset: %s | error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
time.Sleep(20 * time.Second)
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getRespManagementRespAssetById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Response asset deleted
log.Printf("Deleted Responsemanagement response asset %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting response asset %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("response asset %s still exists", d.Id()), resp))
})
}
package responsemanagement_responseasset
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/validators"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_responsemanagement_responseasset_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the responsemanagement_responseasset resource.
3. The datasource schema definitions for the responsemanagement_responseasset datasource.
4. The resource exporter configuration for the responsemanagement_responseasset exporter.
*/
const resourceName = "genesyscloud_responsemanagement_responseasset"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceResponseManagementResponseAsset())
regInstance.RegisterDataSource(resourceName, DataSourceResponseManagementResponseAsset())
regInstance.RegisterExporter(resourceName, ExporterResponseManagementResponseAsset())
}
// ResourceResponsemanagementResponseasset registers the genesyscloud_responsemanagement_responseasset resource with Terraform
func ResourceResponseManagementResponseAsset() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud responsemanagement response asset`,
CreateContext: provider.CreateWithPooledClient(createRespManagementRespAsset),
ReadContext: provider.ReadWithPooledClient(readRespManagementRespAsset),
UpdateContext: provider.UpdateWithPooledClient(updateRespManagementRespAsset),
DeleteContext: provider.DeleteWithPooledClient(deleteRespManagementRespAsset),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`filename`: {
Description: "Name of the file to upload. Changing the name attribute will cause the existing response asset to be dropped and recreated with a new ID. It must not start with a dot and not end with a forward slash. Whitespace and the following characters are not allowed: \\{^}%`]\">[~<#|",
Required: true,
ForceNew: true,
Type: schema.TypeString,
ValidateDiagFunc: validators.ValidateResponseAssetName,
},
`division_id`: {
Description: `Division to associate to this asset. Can only be used with this division.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
"file_content_hash": {
Description: "Hash value of the response asset file content. Used to detect changes.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func DataSourceResponseManagementResponseAsset() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Response Management Response Assets. Select a response asset by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceResponseManagementResponseAssetRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Response asset name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func ExporterResponseManagementResponseAsset() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllResponseAssets),
CustomFileWriter: resourceExporter.CustomFileWriterSettings{
RetrieveAndWriteFilesFunc: responsemanagementResponseassetResolver,
SubDirectory: "response_assets",
},
}
}
package responsemanagement_responseasset
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util/files"
)
func responsemanagementResponseassetResolver(responseAssetId, exportDirectory, subDirectory string, configMap map[string]interface{}, meta interface{}) error {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRespManagementRespAssetProxy(sdkConfig)
fullPath := path.Join(exportDirectory, subDirectory)
if err := os.MkdirAll(fullPath, os.ModePerm); err != nil {
return err
}
ctx := context.Background()
data, _, err := proxy.getRespManagementRespAssetById(ctx, responseAssetId)
if err != nil {
return err
}
baseName := strings.TrimSuffix(filepath.Base(*data.Name), filepath.Ext(*data.Name))
fileName := fmt.Sprintf("%s-%s%s", baseName, responseAssetId, filepath.Ext(*data.Name))
exportFilename := path.Join(subDirectory, fileName)
if err := files.DownloadExportFile(fullPath, fileName, *data.ContentLocation); err != nil {
return err
}
configMap["filename"] = exportFilename
configMap["file_content_hash"] = fmt.Sprintf(`${filesha256("%s")}`, exportFilename)
return nil
}
func GenerateResponseManagementResponseAssetResource(resourceId string, fileName string, divisionId string) string {
fullyQualifiedPath, _ := filepath.Abs(fileName)
return fmt.Sprintf(`
resource "genesyscloud_responsemanagement_responseasset" "%s" {
filename = "%s"
division_id = %s
file_content_hash = filesha256("%s")
}
`, resourceId, fileName, divisionId, fullyQualifiedPath)
}
package routing_email_route
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
)
/*
The genesyscloud_routing_email_route_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *routingEmailRouteProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, inboundRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error)
type getAllRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, name string) (*map[string][]platformclientv2.Inboundroute, *platformclientv2.APIResponse, error)
type getRoutingEmailRouteIdByNameFunc func(ctx context.Context, p *routingEmailRouteProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error)
type getRoutingEmailRouteByIdFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, id string) (inboundRoute *platformclientv2.Inboundroute, response *platformclientv2.APIResponse, err error)
type updateRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, id string, domainId string, inboundRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error)
type deleteRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, id string) (response *platformclientv2.APIResponse, err error)
// routingEmailRouteProxy contains all of the methods that call genesys cloud APIs.
type routingEmailRouteProxy struct {
clientConfig *platformclientv2.Configuration
routingApi *platformclientv2.RoutingApi
createRoutingEmailRouteAttr createRoutingEmailRouteFunc
getAllRoutingEmailRouteAttr getAllRoutingEmailRouteFunc
getRoutingEmailRouteIdByNameAttr getRoutingEmailRouteIdByNameFunc
getRoutingEmailRouteByIdAttr getRoutingEmailRouteByIdFunc
updateRoutingEmailRouteAttr updateRoutingEmailRouteFunc
deleteRoutingEmailRouteAttr deleteRoutingEmailRouteFunc
}
// newRoutingEmailRouteProxy initializes the routing email route proxy with all of the data needed to communicate with Genesys Cloud
func newRoutingEmailRouteProxy(clientConfig *platformclientv2.Configuration) *routingEmailRouteProxy {
api := platformclientv2.NewRoutingApiWithConfig(clientConfig)
return &routingEmailRouteProxy{
clientConfig: clientConfig,
routingApi: api,
createRoutingEmailRouteAttr: createRoutingEmailRouteFn,
getAllRoutingEmailRouteAttr: getAllRoutingEmailRouteFn,
getRoutingEmailRouteIdByNameAttr: getRoutingEmailRouteIdByNameFn,
getRoutingEmailRouteByIdAttr: getRoutingEmailRouteByIdFn,
updateRoutingEmailRouteAttr: updateRoutingEmailRouteFn,
deleteRoutingEmailRouteAttr: deleteRoutingEmailRouteFn,
}
}
// getRoutingEmailRouteProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getRoutingEmailRouteProxy(clientConfig *platformclientv2.Configuration) *routingEmailRouteProxy {
if internalProxy == nil {
internalProxy = newRoutingEmailRouteProxy(clientConfig)
}
return internalProxy
}
// createRoutingEmailRoute creates a Genesys Cloud routing email route
func (p *routingEmailRouteProxy) createRoutingEmailRoute(ctx context.Context, domainId string, routingEmailRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
return p.createRoutingEmailRouteAttr(ctx, p, domainId, routingEmailRoute)
}
// getRoutingEmailRoute retrieves all Genesys Cloud routing email route
func (p *routingEmailRouteProxy) getAllRoutingEmailRoute(ctx context.Context, domainId string, name string) (*map[string][]platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
return p.getAllRoutingEmailRouteAttr(ctx, p, domainId, name)
}
// getRoutingEmailRouteIdByName returns a single Genesys Cloud routing email route by a name
func (p *routingEmailRouteProxy) getRoutingEmailRouteIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) {
return p.getRoutingEmailRouteIdByNameAttr(ctx, p, name)
}
// getRoutingEmailRouteById returns a single Genesys Cloud routing email route by Id
func (p *routingEmailRouteProxy) getRoutingEmailRouteById(ctx context.Context, domainId string, id string) (routingEmailRoute *platformclientv2.Inboundroute, response *platformclientv2.APIResponse, err error) {
return p.getRoutingEmailRouteByIdAttr(ctx, p, domainId, id)
}
// updateRoutingEmailRoute updates a Genesys Cloud routing email route
func (p *routingEmailRouteProxy) updateRoutingEmailRoute(ctx context.Context, id string, domainId string, routingEmailRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
return p.updateRoutingEmailRouteAttr(ctx, p, id, domainId, routingEmailRoute)
}
// deleteRoutingEmailRoute deletes a Genesys Cloud routing email route by Id
func (p *routingEmailRouteProxy) deleteRoutingEmailRoute(ctx context.Context, domainId string, id string) (response *platformclientv2.APIResponse, err error) {
return p.deleteRoutingEmailRouteAttr(ctx, p, domainId, id)
}
func getAllRoutingEmailRouteByDomainIdFn(ctx context.Context, p *routingEmailRouteProxy, domains *platformclientv2.Inbounddomainentitylisting, name string) (*map[string][]platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
var allInboundRoutes = make(map[string][]platformclientv2.Inboundroute)
var apiResponse *platformclientv2.APIResponse
for _, domain := range *domains.Entities {
var allDomainRoutes []platformclientv2.Inboundroute
for pageNum := 1; pageNum <= *domains.PageCount; pageNum++ {
routes, resp, err := p.routingApi.GetRoutingEmailDomainRoutes(*domain.Id, 100, pageNum, name)
if err != nil {
apiResponse = resp
return nil, apiResponse, fmt.Errorf("Failed to get routing email route: %s", err)
}
if routes.Entities == nil || len(*routes.Entities) == 0 {
break
}
allDomainRoutes = append(allDomainRoutes, *routes.Entities...)
allInboundRoutes[*domain.Id] = allDomainRoutes
}
}
return &allInboundRoutes, apiResponse, nil
}
// getAllRoutingEmailRouteFn is the implementation for retrieving all routing email route in Genesys Cloud
func getAllRoutingEmailRouteFn(ctx context.Context, p *routingEmailRouteProxy, domainId string, name string) (*map[string][]platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
var allInboundRoutes = make(map[string][]platformclientv2.Inboundroute)
const pageSize = 100
var apiResponse *platformclientv2.APIResponse
domains, resp, err := p.routingApi.GetRoutingEmailDomains(pageSize, 1, false, "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get routing email domains: %s", err)
}
if domains.Entities == nil || len(*domains.Entities) == 0 {
return &allInboundRoutes, resp, nil
}
// If domainID is given, we only return the routes for that specific domain
if domainId != "" {
return getAllRoutingEmailRouteByDomainIdFn(ctx, p, domains, name)
}
// DomainID not given so we must acquire every route for every domain
routes, _, err := getAllRoutingEmailRouteByDomainIdFn(ctx, p, domains, name)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get routing email domains: %s", err)
}
allInboundRoutes = *routes
for pageNum := 2; pageNum <= *domains.PageCount; pageNum++ {
domains, resp, err := p.routingApi.GetRoutingEmailDomains(pageSize, pageNum, false, "")
if err != nil {
return nil, resp, fmt.Errorf("Failed to get routing email domains: %s", err)
}
if domains.Entities == nil || len(*domains.Entities) == 0 {
return &allInboundRoutes, resp, nil
}
routes, _, err := getAllRoutingEmailRouteByDomainIdFn(ctx, p, domains, name)
if err != nil {
return nil, resp, fmt.Errorf("Failed to get routing email domains: %s", err)
}
allInboundRoutes = *routes
}
return &allInboundRoutes, apiResponse, nil
}
// createRoutingEmailRouteFn is an implementation function for creating a Genesys Cloud routing email route
func createRoutingEmailRouteFn(ctx context.Context, p *routingEmailRouteProxy, domainId string, routingEmailRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
inboundRoute, resp, err := p.routingApi.PostRoutingEmailDomainRoutes(domainId, *routingEmailRoute)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create routing email route: %s", err)
}
return inboundRoute, resp, nil
}
// updateRoutingEmailRouteFn is an implementation of the function to update a Genesys Cloud routing email route
func updateRoutingEmailRouteFn(ctx context.Context, p *routingEmailRouteProxy, id string, domainId string, routingEmailRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
inboundRoute, resp, err := p.routingApi.PutRoutingEmailDomainRoute(domainId, id, *routingEmailRoute)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update routing email route: %s", err)
}
return inboundRoute, resp, nil
}
// deleteRoutingEmailRouteFn is an implementation function for deleting a Genesys Cloud routing email route
func deleteRoutingEmailRouteFn(ctx context.Context, p *routingEmailRouteProxy, domainId string, id string) (*platformclientv2.APIResponse, error) {
resp, err := p.routingApi.DeleteRoutingEmailDomainRoute(domainId, id)
if err != nil {
return resp, fmt.Errorf("Failed to delete routing email route: %s", err)
}
return resp, nil
}
// getRoutingEmailRouteByIdFn is an implementation of the function to get a Genesys Cloud routing email route by Id
func getRoutingEmailRouteByIdFn(ctx context.Context, p *routingEmailRouteProxy, domainId string, id string) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) {
inboundRoute, resp, err := p.routingApi.GetRoutingEmailDomainRoute(domainId, id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve routing email route by id %s: %s", id, err)
}
return inboundRoute, resp, nil
}
// getRoutingEmailRouteIdByNameFn is an implementation of the function to get a Genesys Cloud routing email route by name
func getRoutingEmailRouteIdByNameFn(ctx context.Context, p *routingEmailRouteProxy, name string) (string, bool, *platformclientv2.APIResponse, error) {
inboundRoutesMap, resp, err := getAllRoutingEmailRouteFn(ctx, p, "", name)
if err != nil {
return "", false, resp, err
}
if inboundRoutesMap == nil || len(*inboundRoutesMap) == 0 {
return "", true, resp, fmt.Errorf("No routing email route found with name %s", name)
}
for _, inboundRoutes := range *inboundRoutesMap {
for _, inboundRoute := range inboundRoutes {
if *inboundRoute.Name == name {
log.Printf("Retrieved the routing email route id %s by name %s", *inboundRoute.Id, name)
return *inboundRoute.Id, false, resp, nil
}
}
}
return "", true, resp, fmt.Errorf("Unable to find routing email route with name %s", name)
}
package routing_email_route
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_routing_email_route.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthRoutingEmailRoute retrieves all of the routing email route via Terraform in the Genesys Cloud and is used for the exporter
func getAllRoutingEmailRoutes(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := newRoutingEmailRouteProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
inboundRoutesMap, respCode, err := proxy.getAllRoutingEmailRoute(ctx, "", "")
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, "Failed to get routing email route", respCode)
}
for domainId, inboundRoutes := range *inboundRoutesMap {
for _, inboundRoute := range inboundRoutes {
resources[*inboundRoute.Id] = &resourceExporter.ResourceMeta{
Name: *inboundRoute.Pattern + domainId,
IdPrefix: domainId + "/",
}
}
}
return resources, nil
}
// createRoutingEmailRoute is used by the routing_email_route resource to create Genesys cloud routing email route
func createRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingEmailRouteProxy(sdkConfig)
domainId := d.Get("domain_id").(string)
routingEmailRoute := getRoutingEmailRouteFromResourceData(d)
replyEmail, err := validateSdkReplyEmailAddress(d)
// Checking the self_reference_route flag and routeId rules
if err != nil {
return util.BuildDiagnosticError(resourceName, "Error occurred while validating the reply email address when creating the record", err)
}
replyDomainID, replyRouteID, _ := extractReplyEmailAddressValue(d)
// If the isSelfReferenceRoute() is set to false, we use the route id provided by the terraform script
if replyEmail && !isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, replyRouteID)
}
log.Printf("Creating routing email route %s", d.Id())
inboundRoute, resp, err := proxy.createRoutingEmailRoute(ctx, domainId, &routingEmailRoute)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create routing email route %s error: %s", *routingEmailRoute.Name, err), resp)
}
d.SetId(*inboundRoute.Id)
log.Printf("Created routing email route %s", *inboundRoute.Id)
// If the isSelfReferenceRoute() is set to true we need grab the route id for the route and reapply the reply address,
if replyEmail && isSelfReferenceRouteSet(d) {
inboundRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, *inboundRoute.Id)
_, resp, err = proxy.updateRoutingEmailRoute(ctx, *inboundRoute.Id, domainId, inboundRoute)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Created routing email route %v %s %s, but failed to update the reply answer route to itself | error: %s", inboundRoute.Pattern, domainId, *inboundRoute.Id, err), resp)
}
}
return readRoutingEmailRoute(ctx, d, meta)
}
// readRoutingEmailRoute is used by the routing_email_route resource to read an routing email route from genesys cloud
func readRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingEmailRouteProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingEmailRoute(), constants.DefaultConsistencyChecks, resourceName)
domainId := d.Get("domain_id").(string)
log.Printf("Reading routing email route %s", d.Id())
var route *platformclientv2.Inboundroute
// The normal GET route API has a long cache TTL (5 minutes) which can result in stale data.
// This can be bypassed by issuing a domain query instead.
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
inboundRoutesMap, resp, getErr := proxy.getAllRoutingEmailRoute(ctx, domainId, "")
if getErr != nil {
if util.IsStatus404(resp) {
d.SetId("")
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read routing email route %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read routing email route %s | error: %s", d.Id(), getErr), resp))
}
for _, inboundRoutes := range *inboundRoutesMap {
for _, queryRoute := range inboundRoutes {
if queryRoute.Id != nil && *queryRoute.Id == d.Id() {
route = &queryRoute
break
}
}
}
if route == nil {
d.SetId("")
return nil
}
resourcedata.SetNillableValue(d, "pattern", route.Pattern)
resourcedata.SetNillableReference(d, "queue_id", route.Queue)
resourcedata.SetNillableValue(d, "priority", route.Priority)
resourcedata.SetNillableReference(d, "language_id", route.Language)
resourcedata.SetNillableValue(d, "from_name", route.FromName)
resourcedata.SetNillableValue(d, "from_email", route.FromEmail)
resourcedata.SetNillableReference(d, "flow_id", route.Flow)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "auto_bcc", route.AutoBcc, flattenAutoBccEmailAddress)
resourcedata.SetNillableReference(d, "spam_flow_id", route.SpamFlow)
if route.Skills != nil {
_ = d.Set("skill_ids", util.SdkDomainEntityRefArrToSet(*route.Skills))
} else {
_ = d.Set("skill_ids", nil)
}
if route.ReplyEmailAddress != nil {
flattenedEmails := flattenReplyEmailAddress(*route.ReplyEmailAddress)
_, _, selfReferenceRoute := extractReplyEmailAddressValue(d)
//Set the self_reference_route
flattenedEmails["self_reference_route"] = selfReferenceRoute
//If the reply points back to the route then set the route_id to nil because we dont need to set the
if selfReferenceRoute {
flattenedEmails["route_id"] = nil
}
_ = d.Set("reply_email_address", []interface{}{flattenedEmails})
} else {
_ = d.Set("reply_email_address", nil)
}
log.Printf("Read routing email route %s %v", d.Id(), route.Name)
return cc.CheckState(d)
})
}
// updateRoutingEmailRoute is used by the routing_email_route resource to update an routing email route in Genesys Cloud
func updateRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingEmailRouteProxy(sdkConfig)
domainId := d.Get("domain_id").(string)
routingEmailRoute := getRoutingEmailRouteFromResourceData(d)
//Checking the self_reference_route flag and routeId rules
replyEmail, err := validateSdkReplyEmailAddress(d)
if err != nil {
return util.BuildDiagnosticError(resourceName, "Error occurred while validating the reply email address while trying to update the record", err)
}
replyDomainID, replyRouteID, _ := extractReplyEmailAddressValue(d)
if replyEmail {
if isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, d.Id())
} else if !isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, replyRouteID)
}
}
log.Printf("Updating routing email route %s", d.Id())
inboundRoute, resp, err := proxy.updateRoutingEmailRoute(ctx, d.Id(), domainId, &routingEmailRoute)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update routing email route %s error: %s", *routingEmailRoute.Name, err), resp)
}
log.Printf("Updated routing email route %s", *inboundRoute.Id)
return readRoutingEmailRoute(ctx, d, meta)
}
// deleteRoutingEmailRoute is used by the routing_email_route resource to delete an routing email route from Genesys cloud
func deleteRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingEmailRouteProxy(sdkConfig)
domainId := d.Get("domain_id").(string)
resp, err := proxy.deleteRoutingEmailRoute(ctx, domainId, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete routing email route %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getRoutingEmailRouteById(ctx, domainId, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted routing email route %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting routing email route %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("routing email route %s still exists", d.Id()), resp))
})
}
package routing_email_route
import (
"terraform-provider-genesyscloud/genesyscloud/provider"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_routing_email_route_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the routing_email_route resource.
3. The datasource schema definitions for the routing_email_route datasource.
4. The resource exporter configuration for the routing_email_route exporter.
*/
const resourceName = "genesyscloud_routing_email_route"
var (
bccEmailResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"email": {
Description: "Email address.",
Type: schema.TypeString,
Required: true,
},
"name": {
Description: "Name associated with the email.",
Type: schema.TypeString,
Optional: true,
},
},
}
)
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceRoutingEmailRoute())
regInstance.RegisterExporter(resourceName, RoutingEmailRouteExporter())
}
func ResourceRoutingEmailRoute() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Routing Email Domain Route",
CreateContext: provider.CreateWithPooledClient(createRoutingEmailRoute),
ReadContext: provider.ReadWithPooledClient(readRoutingEmailRoute),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingEmailRoute),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingEmailRoute),
Importer: &schema.ResourceImporter{
StateContext: importRoutingEmailRoute,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"domain_id": {
Description: "ID of the routing domain such as: 'example.com'. Changing the domain_id attribute will cause the email_route object to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"pattern": {
Description: "The search pattern that the mailbox name should match.",
Type: schema.TypeString,
Required: true,
},
"from_name": {
Description: "The sender name to use for outgoing replies.",
Type: schema.TypeString,
Required: true,
},
"from_email": {
Description: "The sender email to use for outgoing replies. This should not be set if reply_email_address is specified.",
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"reply_email_address"},
},
"queue_id": {
Description: "The queue to route the emails to. This should not be set if a flow_id is specified.",
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"flow_id"},
},
"priority": {
Description: "The priority to use for routing.",
Type: schema.TypeInt,
Optional: true,
},
"skill_ids": {
Description: "The skills to use for routing.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"language_id": {
Description: "The language to use for routing.",
Type: schema.TypeString,
Optional: true,
},
"flow_id": {
Description: "The flow to use for processing the email. This should not be set if a queue_id is specified.",
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"queue_id"},
},
"reply_email_address": {
Description: "The route to use for email replies. This should not be set if from_email or auto_bcc are specified.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ConflictsWith: []string{"from_email"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"domain_id": {
Description: "Domain of the route.",
Type: schema.TypeString,
Required: true,
},
"route_id": {
Description: "ID of the route.",
Type: schema.TypeString,
Required: false,
Optional: true,
},
"self_reference_route": {
Description: `Use this route as the reply email address. If true you will use the route id for this resource as the reply and you
can not set a route. If you set this value to false (or leave the attribute off)you must set a route id.`,
Type: schema.TypeBool,
Required: false,
Optional: true,
Default: false,
},
},
},
},
"auto_bcc": {
Description: "The recipients that should be automatically blind copied on outbound emails associated with this route. This should not be set if reply_email_address is specified.",
Type: schema.TypeSet,
Optional: true,
Elem: bccEmailResource,
ConflictsWith: []string{"reply_email_address"},
},
"spam_flow_id": {
Description: "The flow to use for processing inbound emails that have been marked as spam.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
// RoutingEmailRouteExporter returns the resourceExporter object used to hold the genesyscloud_routing_email_route exporter's config
func RoutingEmailRouteExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingEmailRoutes),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"domain_id": {RefType: "genesyscloud_routing_email_domain"},
"queue_id": {RefType: "genesyscloud_routing_queue"},
"skill_ids": {RefType: "genesyscloud_routing_skill"},
"language_id": {RefType: "genesyscloud_routing_language"},
"flow_id": {RefType: "genesyscloud_flow"},
"spam_flow_id": {RefType: "genesyscloud_flow"},
"reply_email_address.domain_id": {RefType: "genesyscloud_routing_email_domain"},
"reply_email_address.route_id": {RefType: "genesyscloud_routing_email_route"},
},
RemoveIfMissing: map[string][]string{
"reply_email_address": {"route_id", "self_reference_route"},
},
CustomAttributeResolver: map[string]*resourceExporter.RefAttrCustomResolver{
"reply_email_address.self_reference_route": {ResolverFunc: resourceExporter.ReplyEmailAddressSelfReferenceRouteExporterResolver},
},
}
}
package routing_email_route
import (
"context"
"fmt"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_routing_email_route_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
// getRoutingEmailRouteFromResourceData maps data from schema ResourceData object to a platformclientv2.Inboundroute
func getRoutingEmailRouteFromResourceData(d *schema.ResourceData) platformclientv2.Inboundroute {
id := d.Id()
return platformclientv2.Inboundroute{
Id: &id,
Pattern: platformclientv2.String(d.Get("pattern").(string)),
Queue: util.BuildSdkDomainEntityRef(d, "queue_id"),
Priority: platformclientv2.Int(d.Get("priority").(int)),
Skills: util.BuildSdkDomainEntityRefArr(d, "skill_ids"),
Language: util.BuildSdkDomainEntityRef(d, "language_id"),
FromName: platformclientv2.String(d.Get("from_name").(string)),
FromEmail: buildFromEmail(d),
Flow: util.BuildSdkDomainEntityRef(d, "flow_id"),
AutoBcc: buildAutoBccEmailAddresses(d),
SpamFlow: util.BuildSdkDomainEntityRef(d, "spam_flow_id"),
}
}
// Build Functions
func buildFromEmail(d *schema.ResourceData) *string {
if d.Get("from_email") != "" {
return platformclientv2.String(d.Get("from_email").(string))
}
return nil
}
func buildAutoBccEmailAddresses(d *schema.ResourceData) *[]platformclientv2.Emailaddress {
if bccAddresses := d.Get("auto_bcc"); bccAddresses != nil {
bccAddressList := bccAddresses.(*schema.Set).List()
sdkEmails := make([]platformclientv2.Emailaddress, len(bccAddressList))
for i, configBcc := range bccAddressList {
bccMap := configBcc.(map[string]interface{})
bccEmail := bccMap["email"].(string)
bccName := bccMap["name"].(string)
sdkEmails[i] = platformclientv2.Emailaddress{
Email: &bccEmail,
Name: &bccName,
}
}
return &sdkEmails
}
return nil
}
func buildReplyEmailAddress(domainID string, routeID string) *platformclientv2.Queueemailaddress {
// For some reason the SDK expects a pointer to a pointer for this property
inboundRoute := &platformclientv2.Inboundroute{
Id: &routeID,
}
result := platformclientv2.Queueemailaddress{
Domain: &platformclientv2.Domainentityref{Id: &domainID},
Route: &inboundRoute,
}
return &result
}
// Flatten Functions
func flattenAutoBccEmailAddress(emailAddress *[]platformclientv2.Emailaddress) []interface{} {
if len(*emailAddress) == 0 {
return nil
}
var emailAddressList []interface{}
for _, emailAddress := range *emailAddress {
emailAddressMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(emailAddressMap, "email", emailAddress.Email)
resourcedata.SetMapValueIfNotNil(emailAddressMap, "name", emailAddress.Name)
emailAddressList = append(emailAddressList, emailAddressMap)
}
return emailAddressList
}
func flattenReplyEmailAddress(settings platformclientv2.Queueemailaddress) map[string]interface{} {
settingsMap := make(map[string]interface{})
resourcedata.SetMapReferenceValueIfNotNil(settingsMap, "domain_id", settings.Domain)
if settings.Route != nil {
route := *settings.Route
settingsMap["route_id"] = *route.Id
}
return settingsMap
}
// Helper Functions
func validateSdkReplyEmailAddress(d *schema.ResourceData) (bool, error) {
replyEmailAddress := d.Get("reply_email_address").([]interface{})
if replyEmailAddress != nil && len(replyEmailAddress) > 0 {
settingsMap := replyEmailAddress[0].(map[string]interface{})
routeID := settingsMap["route_id"].(string)
selfReferenceRoute := settingsMap["self_reference_route"].(bool)
if selfReferenceRoute && routeID != "" {
return true, fmt.Errorf("can not set a reply email address route id directly, if the self_reference_route value is set to true")
}
if !selfReferenceRoute && routeID == "" {
return true, fmt.Errorf("you must provide reply email address route id if the self_reference_route value is set to false")
}
return true, nil
}
return false, nil
}
func extractReplyEmailAddressValue(d *schema.ResourceData) (string, string, bool) {
replyEmailAddress := d.Get("reply_email_address").([]interface{})
if replyEmailAddress != nil && len(replyEmailAddress) > 0 {
settingsMap := replyEmailAddress[0].(map[string]interface{})
return settingsMap["domain_id"].(string), settingsMap["route_id"].(string), settingsMap["self_reference_route"].(bool)
}
return "", "", false
}
func isSelfReferenceRouteSet(d *schema.ResourceData) bool {
replyEmailAddress := d.Get("reply_email_address").([]interface{})
if replyEmailAddress != nil && len(replyEmailAddress) > 0 {
settingsMap := replyEmailAddress[0].(map[string]interface{})
return settingsMap["self_reference_route"].(bool)
}
return false
}
func importRoutingEmailRoute(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
// Import must specify domain ID and route ID
idParts := strings.Split(d.Id(), "/")
if len(idParts) < 2 {
return nil, fmt.Errorf("invalid email route import ID %s", d.Id())
}
_ = d.Set("domain_id", idParts[0])
d.SetId(idParts[1])
return []*schema.ResourceData{d}, nil
}
func GenerateRoutingEmailRouteResource(
resourceID string,
domainID string,
pattern string,
fromName string,
otherAttrs ...string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_email_route" "%s" {
domain_id = %s
pattern = "%s"
from_name = "%s"
%s
}
`, resourceID, domainID, pattern, fromName, strings.Join(otherAttrs, "\n"))
}
package routing_queue
import (
"context"
"fmt"
"log"
"strings"
"sync"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// Cache for Data Sources
type DataSourceCache struct {
cache map[string]string
mutex sync.RWMutex
clientConfig *platformclientv2.Configuration
hydrateCacheFunc func(*DataSourceCache) error
}
var (
dataSourceRoutingQueueCache *DataSourceCache
)
func dataSourceRoutingQueueRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
// Create a cache for the queues
if dataSourceRoutingQueueCache == nil {
dataSourceRoutingQueueCache = NewDataSourceCache(sdkConfig, hydrateRoutingQueueCacheFn)
}
if err := dataSourceRoutingQueueCache.hydrateCacheIfEmpty(); err != nil {
return diag.FromErr(err)
}
// Get id from cache
name := d.Get("name").(string)
queueId, ok := dataSourceRoutingQueueCache.get(normalizeQueueName(name))
if !ok {
// If not found in cache, try to obtain through SDK call
log.Printf("could not find routing queue %v in cache. Will try API to find value", name)
queueId, diagErr := getQueueByName(ctx, routingApi, name)
if diagErr != nil {
return diagErr
}
d.SetId(queueId)
if err := dataSourceRoutingQueueCache.updateCacheEntry(name, queueId); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("error updating cache"), err)
}
return nil
}
log.Printf("found queue %v from cache", name)
d.SetId(queueId)
return nil
}
func (c *DataSourceCache) hydrateCacheIfEmpty() error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.isEmpty() {
if err := c.hydrateCache(); err != nil {
return err
}
}
return nil
}
// Normalize queue name for keys in the cache
func normalizeQueueName(queueName string) string {
return strings.ToLower(queueName)
}
// NewDataSourceCache creates a new data source cache
func NewDataSourceCache(clientConfig *platformclientv2.Configuration, hydrateFn func(*DataSourceCache) error) *DataSourceCache {
return &DataSourceCache{
cache: make(map[string]string),
clientConfig: clientConfig,
hydrateCacheFunc: hydrateFn,
}
}
// hydrateRoutingQueueCacheFn for hydrating the cache with Genesys Cloud routing queues using the SDK
func hydrateRoutingQueueCacheFn(c *DataSourceCache) error {
log.Printf("hydrating cache for data source genesyscloud_routing_queues")
routingApi := platformclientv2.NewRoutingApiWithConfig(c.clientConfig)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
queues, _, getErr := routingApi.GetRoutingQueues(pageNum, pageSize, "", "", nil, nil, nil, "", false)
if getErr != nil {
return fmt.Errorf("failed to get page of queues: %v", getErr)
}
if queues.Entities == nil || len(*queues.Entities) == 0 {
break
}
// Add ids to cache
for _, queue := range *queues.Entities {
c.cache[normalizeQueueName(*queue.Name)] = *queue.Id
}
}
log.Printf("cache hydration completed for data source genesyscloud_routing_queues")
return nil
}
// Get queue by name.
// Returns the queue id (blank if not found) and diag
func getQueueByName(ctx context.Context, routingApi *platformclientv2.RoutingApi, name string) (string, diag.Diagnostics) {
queueId := ""
diag := util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
queues, resp, getErr := routingApi.GetRoutingQueues(pageNum, pageSize, "", name, nil, nil, nil, "", false)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting queue %s | error %s", name, getErr), resp))
}
if queues.Entities == nil || len(*queues.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no routing queues found with name %s", name), resp))
}
for _, queue := range *queues.Entities {
if queue.Name != nil && normalizeQueueName(*queue.Name) == normalizeQueueName(name) {
queueId = *queue.Id
return nil
}
}
}
})
return queueId, diag
}
// Hydrate the cache with updated values.
func (c *DataSourceCache) hydrateCache() error {
return c.hydrateCacheFunc(c)
}
// Adds or updates a cache entry
func (c *DataSourceCache) updateCacheEntry(key string, val string) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.cache == nil {
return fmt.Errorf("cache is not initialized")
}
c.cache[key] = val
log.Printf("updated cache entry [%v] to value: %v", key, val)
return nil
}
// Returns true if the cache is empty
func (c *DataSourceCache) isEmpty() bool {
return len(c.cache) <= 0
}
// Get value (resource id) from cache by key string
// If value is not found return empty string and `false`
func (c *DataSourceCache) get(key string) (val string, isFound bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
if c.isEmpty() {
log.Printf("cache is empty. Hydrate it first with values")
return "", false
}
queueId, ok := c.cache[key]
if !ok {
log.Printf("cache miss. cannot find key %s", key)
return "", false
}
log.Printf("cache hit. found key %v in cache with value %v", key, queueId)
return queueId, true
}
package routing_queue
import (
"context"
"fmt"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_routing_queue_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *RoutingQueueProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllRoutingQueuesFunc func(ctx context.Context, p *RoutingQueueProxy) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error)
type getRoutingQueueByIdFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error)
type getRoutingQueueWrapupCodeIdsFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string) ([]string, *platformclientv2.APIResponse, error)
// RoutingQueueProxy contains all the methods that call genesys cloud APIs.
type RoutingQueueProxy struct {
clientConfig *platformclientv2.Configuration
routingApi *platformclientv2.RoutingApi
getAllRoutingQueuesAttr getAllRoutingQueuesFunc
getRoutingQueueByIdAttr getRoutingQueueByIdFunc
getRoutingQueueWrapupCodeIdsAttr getRoutingQueueWrapupCodeIdsFunc
RoutingQueueCache rc.CacheInterface[platformclientv2.Queue]
}
// newRoutingQueuesProxy initializes the routing queue proxy with all the data needed to communicate with Genesys Cloud
func newRoutingQueuesProxy(clientConfig *platformclientv2.Configuration) *RoutingQueueProxy {
api := platformclientv2.NewRoutingApiWithConfig(clientConfig)
routingQueueCache := rc.NewResourceCache[platformclientv2.Queue]()
return &RoutingQueueProxy{
clientConfig: clientConfig,
routingApi: api,
getAllRoutingQueuesAttr: getAllRoutingQueuesFn,
getRoutingQueueByIdAttr: getRoutingQueueByIdFn,
getRoutingQueueWrapupCodeIdsAttr: getRoutingQueueWrapupCodeIdsFn,
RoutingQueueCache: routingQueueCache,
}
}
// GetRoutingQueueProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func GetRoutingQueueProxy(clientConfig *platformclientv2.Configuration) *RoutingQueueProxy {
if internalProxy == nil {
internalProxy = newRoutingQueuesProxy(clientConfig)
}
return internalProxy
}
// GetAllRoutingQueues retrieves all Genesys Cloud routing queues
func (p *RoutingQueueProxy) GetAllRoutingQueues(ctx context.Context) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) {
return p.getAllRoutingQueuesAttr(ctx, p)
}
// getRoutingQueueById returns a single Genesys Cloud Routing Queue by ID
func (p *RoutingQueueProxy) getRoutingQueueById(ctx context.Context, queueId string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) {
return p.getRoutingQueueByIdAttr(ctx, p, queueId)
}
// getRoutingQueueWrapupCodeIds returns a list of routing queue wrapup code ids
func (p *RoutingQueueProxy) getRoutingQueueWrapupCodeIds(ctx context.Context, queueId string) ([]string, *platformclientv2.APIResponse, error) {
return p.getRoutingQueueWrapupCodeIdsAttr(ctx, p, queueId)
}
// getAllRoutingQueuesFn is the implementation for retrieving all routing queues in Genesys Cloud
func getAllRoutingQueuesFn(ctx context.Context, p *RoutingQueueProxy) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) {
var allQueues []platformclientv2.Queue
const pageSize = 100
queues, resp, getErr := p.routingApi.GetRoutingQueues(1, pageSize, "", "", nil, nil, nil, "", false)
if getErr != nil {
return nil, resp, fmt.Errorf("failed to get first page of queues: %v", getErr)
}
// Check if the routing queue cache is populated with all the data, if it is, return that instead
// If the size of the cache is the same as the total number of queues, the cache is up-to-date
if rc.GetCacheSize(p.RoutingQueueCache) == *queues.Total && rc.GetCacheSize(p.RoutingQueueCache) != 0 {
return rc.GetCache(p.RoutingQueueCache), nil, nil
} else if rc.GetCacheSize(p.RoutingQueueCache) != *queues.Total && rc.GetCacheSize(p.RoutingQueueCache) != 0 {
// The cache is populated but not with the right data, clear the cache so it can be re populated
p.RoutingQueueCache = rc.NewResourceCache[platformclientv2.Queue]()
}
if queues.Entities == nil || len(*queues.Entities) == 0 {
return &allQueues, resp, nil
}
allQueues = append(allQueues, *queues.Entities...)
for pageNum := 2; pageNum <= *queues.PageCount; pageNum++ {
queues, resp, getErr := p.routingApi.GetRoutingQueues(pageNum, pageSize, "", "", nil, nil, nil, "", false)
if getErr != nil {
return nil, resp, fmt.Errorf("failed to get page of queues: %v", getErr)
}
if queues.Entities == nil || len(*queues.Entities) == 0 {
break
}
allQueues = append(allQueues, *queues.Entities...)
}
for _, queue := range allQueues {
rc.SetCache(p.RoutingQueueCache, *queue.Id, queue)
}
return &allQueues, resp, nil
}
// getRoutingQueueByIdFn is the implementation for retrieving a routing queues in Genesys Cloud
func getRoutingQueueByIdFn(ctx context.Context, p *RoutingQueueProxy, queueId string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) {
queue := rc.GetCacheItem(p.RoutingQueueCache, queueId)
if queue != nil {
return queue, nil, nil
}
queue, resp, err := p.routingApi.GetRoutingQueue(queueId)
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve routing queue by id %s: %s", queueId, err)
}
return queue, resp, nil
}
func getRoutingQueueWrapupCodeIdsFn(ctx context.Context, p *RoutingQueueProxy, queueId string) ([]string, *platformclientv2.APIResponse, error) {
var codeIds []string
const pageSize = 100
codes, resp, err := p.routingApi.GetRoutingQueueWrapupcodes(queueId, pageSize, 1)
if err != nil {
return nil, resp, fmt.Errorf("failed to first page of wrapup codes for queue %s: %s", queueId, err)
}
if codes == nil || codes.Entities == nil || len(*codes.Entities) == 0 {
return codeIds, resp, nil
}
for _, code := range *codes.Entities {
codeIds = append(codeIds, *code.Id)
}
for pageNum := 2; pageNum <= *codes.PageCount; pageNum++ {
codes, resp, err := p.routingApi.GetRoutingQueueWrapupcodes(queueId, pageSize, pageNum)
if err != nil {
return nil, resp, fmt.Errorf("failed to page of wrapup codes for queue %s: %s", queueId, err)
}
if codes == nil || codes.Entities != nil || len(*codes.Entities) == 0 {
break
}
for _, code := range *codes.Entities {
codeIds = append(codeIds, *code.Id)
}
}
return codeIds, resp, nil
}
package routing_queue
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
featureToggles "terraform-provider-genesyscloud/genesyscloud/util/feature_toggles"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
chunksProcess "terraform-provider-genesyscloud/genesyscloud/util/chunks"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var bullseyeExpansionTypeTimeout = "TIMEOUT_SECONDS"
func getAllRoutingQueues(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := GetRoutingQueueProxy(clientConfig)
// Newly created resources often aren't returned unless there's a delay
time.Sleep(5 * time.Second)
queues, resp, err := proxy.GetAllRoutingQueues(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to get routing queues"), resp)
}
for _, queue := range *queues {
resources[*queue.Id] = &resourceExporter.ResourceMeta{Name: *queue.Name}
}
return resources, nil
}
func createQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
divisionID := d.Get("division_id").(string)
scoringMethod := d.Get("scoring_method").(string)
skillGroups := buildMemberGroupList(d, "skill_groups", "SKILLGROUP")
groups := buildMemberGroupList(d, "groups", "GROUP")
teams := buildMemberGroupList(d, "teams", "TEAM")
memberGroups := append(*skillGroups, *groups...)
memberGroups = append(memberGroups, *teams...)
createQueue := platformclientv2.Createqueuerequest{
Name: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
MediaSettings: buildSdkMediaSettings(d),
RoutingRules: buildSdkRoutingRules(d),
Bullseye: buildSdkBullseyeSettings(d),
AcwSettings: buildSdkAcwSettings(d),
AgentOwnedRouting: constructAgentOwnedRouting(d),
SkillEvaluationMethod: platformclientv2.String(d.Get("skill_evaluation_method").(string)),
QueueFlow: util.BuildSdkDomainEntityRef(d, "queue_flow_id"),
EmailInQueueFlow: util.BuildSdkDomainEntityRef(d, "email_in_queue_flow_id"),
MessageInQueueFlow: util.BuildSdkDomainEntityRef(d, "message_in_queue_flow_id"),
WhisperPrompt: util.BuildSdkDomainEntityRef(d, "whisper_prompt_id"),
AutoAnswerOnly: platformclientv2.Bool(d.Get("auto_answer_only").(bool)),
CallingPartyName: platformclientv2.String(d.Get("calling_party_name").(string)),
CallingPartyNumber: platformclientv2.String(d.Get("calling_party_number").(string)),
DefaultScripts: buildSdkDefaultScriptsMap(d),
OutboundMessagingAddresses: buildSdkQueueMessagingAddresses(d),
EnableTranscription: platformclientv2.Bool(d.Get("enable_transcription").(bool)),
SuppressInQueueCallRecording: platformclientv2.Bool(d.Get("suppress_in_queue_call_recording").(bool)),
EnableManualAssignment: platformclientv2.Bool(d.Get("enable_manual_assignment").(bool)),
DirectRouting: buildSdkDirectRouting(d),
MemberGroups: &memberGroups,
}
if exists := featureToggles.CSGToggleExists(); !exists {
conditionalGroupRouting, diagErr := buildSdkConditionalGroupRouting(d)
if diagErr != nil {
return diagErr
}
createQueue.ConditionalGroupRouting = conditionalGroupRouting
} else {
log.Printf("%s is set, not creating conditional_group_routing_rules attribute in routing_queue %s resource", featureToggles.CSGToggleName(), d.Id())
}
if exists := featureToggles.OEAToggleExists(); !exists {
createQueue.OutboundEmailAddress = buildSdkQueueEmailAddress(d)
} else {
log.Printf("%s is set, not creating outbound_email_address attribute in routing_queue %s resource", featureToggles.OEAToggleName(), d.Id())
}
if divisionID != "" {
createQueue.Division = &platformclientv2.Writabledivision{Id: &divisionID}
}
if scoringMethod != "" {
createQueue.ScoringMethod = &scoringMethod
}
queue, resp, err := routingAPI.PostRoutingQueues(createQueue)
if err != nil {
log.Printf("error while trying to create queue: %s. Err %s", *createQueue.Name, err)
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create queue %s error: %s", *createQueue.Name, err), resp)
}
if resp.StatusCode != http.StatusOK {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create queue %s with error: %s, status code %v", *createQueue.Name, err, resp.StatusCode), resp)
}
d.SetId(*queue.Id)
diagErr := updateQueueMembers(d, sdkConfig)
if diagErr != nil {
return diagErr
}
diagErr = updateQueueWrapupCodes(d, routingAPI)
if diagErr != nil {
return diagErr
}
return readQueue(ctx, d, meta)
}
func readQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := GetRoutingQueueProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingQueue(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading queue %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
currentQueue, resp, getErr := proxy.getRoutingQueueById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read queue %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read queue %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", currentQueue.Name)
resourcedata.SetNillableValue(d, "description", currentQueue.Description)
resourcedata.SetNillableValue(d, "skill_evaluation_method", currentQueue.SkillEvaluationMethod)
resourcedata.SetNillableReferenceDivision(d, "division_id", currentQueue.Division)
_ = d.Set("acw_wrapup_prompt", nil)
_ = d.Set("acw_timeout_ms", nil)
if currentQueue.AcwSettings != nil {
resourcedata.SetNillableValue(d, "acw_wrapup_prompt", currentQueue.AcwSettings.WrapupPrompt)
resourcedata.SetNillableValue(d, "acw_timeout_ms", currentQueue.AcwSettings.TimeoutMs)
}
_ = d.Set("media_settings_call", nil)
_ = d.Set("media_settings_callback", nil)
_ = d.Set("media_settings_chat", nil)
_ = d.Set("media_settings_email", nil)
_ = d.Set("media_settings_message", nil)
_ = d.Set("agent_owned_routing", nil)
if currentQueue.MediaSettings != nil {
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "media_settings_call", currentQueue.MediaSettings.Call, flattenMediaSetting)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "media_settings_callback", currentQueue.MediaSettings.Callback, flattenMediaSettingCallback)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "media_settings_chat", currentQueue.MediaSettings.Chat, flattenMediaSetting)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "media_settings_email", currentQueue.MediaSettings.Email, flattenMediaSetting)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "media_settings_message", currentQueue.MediaSettings.Message, flattenMediaSetting)
}
if currentQueue.AgentOwnedRouting != nil {
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "agent_owned_routing", currentQueue.AgentOwnedRouting, flattenAgentOwnedRouting)
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "routing_rules", currentQueue.RoutingRules, flattenRoutingRules)
if currentQueue.Bullseye != nil {
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "bullseye_rings", currentQueue.Bullseye.Rings, flattenBullseyeRings)
}
resourcedata.SetNillableReference(d, "queue_flow_id", currentQueue.QueueFlow)
resourcedata.SetNillableReference(d, "message_in_queue_flow_id", currentQueue.MessageInQueueFlow)
resourcedata.SetNillableReference(d, "email_in_queue_flow_id", currentQueue.EmailInQueueFlow)
resourcedata.SetNillableReference(d, "whisper_prompt_id", currentQueue.WhisperPrompt)
resourcedata.SetNillableValue(d, "auto_answer_only", currentQueue.AutoAnswerOnly)
resourcedata.SetNillableValue(d, "enable_transcription", currentQueue.EnableTranscription)
resourcedata.SetNillableValue(d, "suppress_in_queue_call_recording", currentQueue.SuppressInQueueCallRecording)
resourcedata.SetNillableValue(d, "enable_manual_assignment", currentQueue.EnableManualAssignment)
resourcedata.SetNillableValue(d, "calling_party_name", currentQueue.CallingPartyName)
resourcedata.SetNillableValue(d, "calling_party_number", currentQueue.CallingPartyNumber)
resourcedata.SetNillableValue(d, "scoring_method", currentQueue.ScoringMethod)
if currentQueue.DefaultScripts != nil {
_ = d.Set("default_script_ids", flattenDefaultScripts(*currentQueue.DefaultScripts))
} else {
_ = d.Set("default_script_ids", nil)
}
if currentQueue.OutboundMessagingAddresses != nil && currentQueue.OutboundMessagingAddresses.SmsAddress != nil {
_ = d.Set("outbound_messaging_sms_address_id", *currentQueue.OutboundMessagingAddresses.SmsAddress.Id)
} else {
_ = d.Set("outbound_messaging_sms_address_id", nil)
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "direct_routing", currentQueue.DirectRouting, flattenDirectRouting)
wrapupCodes, err := flattenQueueWrapupCodes(ctx, d.Id(), proxy)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
_ = d.Set("wrapup_codes", wrapupCodes)
members, err := flattenQueueMembers(d.Id(), "user", sdkConfig)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
_ = d.Set("members", members)
skillGroup := "SKILLGROUP"
team := "TEAM"
group := "GROUP"
_ = d.Set("skill_groups", flattenQueueMemberGroupsList(currentQueue, &skillGroup))
_ = d.Set("teams", flattenQueueMemberGroupsList(currentQueue, &team))
_ = d.Set("groups", flattenQueueMemberGroupsList(currentQueue, &group))
if exists := featureToggles.CSGToggleExists(); !exists {
_ = d.Set("conditional_group_routing_rules", flattenConditionalGroupRoutingRules(currentQueue))
} else {
log.Printf("%s is set, not reading conditional_group_routing_rules attribute in routing_queue %s resource", featureToggles.CSGToggleName(), d.Id())
}
if exists := featureToggles.OEAToggleExists(); !exists {
if currentQueue.OutboundEmailAddress != nil && *currentQueue.OutboundEmailAddress != nil {
outboundEmailAddress := *currentQueue.OutboundEmailAddress
_ = d.Set("outbound_email_address", []interface{}{FlattenQueueEmailAddress(*outboundEmailAddress)})
} else {
_ = d.Set("outbound_email_address", nil)
}
} else {
log.Printf("%s is set, not reading outbound_email_address attribute in routing_queue %s resource", featureToggles.OEAToggleName(), d.Id())
}
log.Printf("Done reading queue %s %s", d.Id(), *currentQueue.Name)
return cc.CheckState(d)
})
}
func updateQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
scoringMethod := d.Get("scoring_method").(string)
skillGroups := buildMemberGroupList(d, "skill_groups", "SKILLGROUP")
groups := buildMemberGroupList(d, "groups", "GROUP")
teams := buildMemberGroupList(d, "teams", "TEAM")
memberGroups := append(*skillGroups, *groups...)
memberGroups = append(memberGroups, *teams...)
updateQueue := platformclientv2.Queuerequest{
Name: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
MediaSettings: buildSdkMediaSettings(d),
RoutingRules: buildSdkRoutingRules(d),
Bullseye: buildSdkBullseyeSettings(d),
AcwSettings: buildSdkAcwSettings(d),
AgentOwnedRouting: constructAgentOwnedRouting(d),
SkillEvaluationMethod: platformclientv2.String(d.Get("skill_evaluation_method").(string)),
QueueFlow: util.BuildSdkDomainEntityRef(d, "queue_flow_id"),
EmailInQueueFlow: util.BuildSdkDomainEntityRef(d, "email_in_queue_flow_id"),
MessageInQueueFlow: util.BuildSdkDomainEntityRef(d, "message_in_queue_flow_id"),
WhisperPrompt: util.BuildSdkDomainEntityRef(d, "whisper_prompt_id"),
AutoAnswerOnly: platformclientv2.Bool(d.Get("auto_answer_only").(bool)),
CallingPartyName: platformclientv2.String(d.Get("calling_party_name").(string)),
CallingPartyNumber: platformclientv2.String(d.Get("calling_party_number").(string)),
DefaultScripts: buildSdkDefaultScriptsMap(d),
OutboundMessagingAddresses: buildSdkQueueMessagingAddresses(d),
EnableTranscription: platformclientv2.Bool(d.Get("enable_transcription").(bool)),
SuppressInQueueCallRecording: platformclientv2.Bool(d.Get("suppress_in_queue_call_recording").(bool)),
EnableManualAssignment: platformclientv2.Bool(d.Get("enable_manual_assignment").(bool)),
DirectRouting: buildSdkDirectRouting(d),
MemberGroups: &memberGroups,
}
if exists := featureToggles.CSGToggleExists(); !exists {
conditionalGroupRouting, diagErr := buildSdkConditionalGroupRouting(d)
if diagErr != nil {
return diagErr
}
updateQueue.ConditionalGroupRouting = conditionalGroupRouting
} else {
log.Printf("%s is set, not updating conditional_group_routing_rules attribute in routing_queue %s resource", featureToggles.CSGToggleName(), d.Id())
}
if exists := featureToggles.OEAToggleExists(); !exists {
updateQueue.OutboundEmailAddress = buildSdkQueueEmailAddress(d)
} else {
log.Printf("%s is set, not creating outbound_email_address attribute in routing_queue %s resource", featureToggles.OEAToggleName(), d.Id())
}
log.Printf("Updating queue %s", *updateQueue.Name)
if scoringMethod != "" {
updateQueue.ScoringMethod = &scoringMethod
}
_, resp, err := routingAPI.PutRoutingQueue(d.Id(), updateQueue)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update queue %s error: %s", *updateQueue.Name, err), resp)
}
diagErr := util.UpdateObjectDivision(d, "QUEUE", sdkConfig)
if diagErr != nil {
return diagErr
}
diagErr = updateQueueMembers(d, sdkConfig)
if diagErr != nil {
return diagErr
}
diagErr = updateQueueWrapupCodes(d, routingAPI)
if diagErr != nil {
return diagErr
}
log.Printf("Finished updating queue %s", *updateQueue.Name)
return readQueue(ctx, d, meta)
}
func deleteQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
log.Printf("Deleting queue %s", name)
resp, err := routingAPI.DeleteRoutingQueue(d.Id(), true)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete queue %s error: %s", name, err), resp)
}
// Queue deletes are not immediate. Query until queue is no longer found
// Add a delay before the first request to reduce the likelihood of public API's cache
// re-populating the queue after the delete. Otherwise it may not expire for a minute.
time.Sleep(5 * time.Second)
//DEVTOOLING-238- Increasing this to a 120 seconds to see if we can temporarily mitigate a problem for a customer
return util.WithRetries(ctx, 120*time.Second, func() *retry.RetryError {
_, resp, err := routingAPI.GetRoutingQueue(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Queue deleted
log.Printf("Queue %s deleted", name)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting queue %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Queue %s still exists", d.Id()), resp))
})
}
func buildSdkMediaSettings(d *schema.ResourceData) *platformclientv2.Queuemediasettings {
queueMediaSettings := &platformclientv2.Queuemediasettings{}
mediaSettingsCall := d.Get("media_settings_call").([]interface{})
if mediaSettingsCall != nil && len(mediaSettingsCall) > 0 {
queueMediaSettings.Call = buildSdkMediaSetting(mediaSettingsCall)
}
mediaSettingsCallback := d.Get("media_settings_callback").([]interface{})
if mediaSettingsCallback != nil && len(mediaSettingsCallback) > 0 {
queueMediaSettings.Callback = buildSdkMediaSettingCallback(mediaSettingsCallback)
}
mediaSettingsChat := d.Get("media_settings_chat").([]interface{})
if mediaSettingsChat != nil && len(mediaSettingsChat) > 0 {
queueMediaSettings.Chat = buildSdkMediaSetting(mediaSettingsChat)
}
mediaSettingsEmail := d.Get("media_settings_email").([]interface{})
log.Printf("The media settings email #%v", mediaSettingsEmail)
if mediaSettingsEmail != nil && len(mediaSettingsEmail) > 0 {
queueMediaSettings.Email = buildSdkMediaSetting(mediaSettingsEmail)
}
mediaSettingsMessage := d.Get("media_settings_message").([]interface{})
if mediaSettingsMessage != nil && len(mediaSettingsMessage) > 0 {
queueMediaSettings.Message = buildSdkMediaSetting(mediaSettingsMessage)
}
return queueMediaSettings
}
func constructAgentOwnedRouting(d *schema.ResourceData) *platformclientv2.Agentownedrouting {
if agentOwnedRouting, ok := d.Get("agent_owned_routing").([]interface{}); ok {
if agentOwnedRouting != nil && len(agentOwnedRouting) > 0 {
return buildAgentOwnedRouting(agentOwnedRouting)
}
}
return nil
}
func buildAgentOwnedRouting(routing []interface{}) *platformclientv2.Agentownedrouting {
settingsMap := routing[0].(map[string]interface{})
return &platformclientv2.Agentownedrouting{
EnableAgentOwnedCallbacks: platformclientv2.Bool(settingsMap["enable_agent_owned_callbacks"].(bool)),
MaxOwnedCallbackDelayHours: platformclientv2.Int(settingsMap["max_owned_callback_delay_hours"].(int)),
MaxOwnedCallbackHours: platformclientv2.Int(settingsMap["max_owned_callback_hours"].(int)),
}
}
func buildSdkMediaSetting(settings []interface{}) *platformclientv2.Mediasettings {
settingsMap := settings[0].(map[string]interface{})
return &platformclientv2.Mediasettings{
AlertingTimeoutSeconds: platformclientv2.Int(settingsMap["alerting_timeout_sec"].(int)),
EnableAutoAnswer: platformclientv2.Bool(settingsMap["enable_auto_answer"].(bool)),
ServiceLevel: &platformclientv2.Servicelevel{
Percentage: platformclientv2.Float64(settingsMap["service_level_percentage"].(float64)),
DurationMs: platformclientv2.Int(settingsMap["service_level_duration_ms"].(int)),
},
}
}
func buildSdkMediaSettingCallback(settings []interface{}) *platformclientv2.Callbackmediasettings {
settingsMap := settings[0].(map[string]interface{})
return &platformclientv2.Callbackmediasettings{
AlertingTimeoutSeconds: platformclientv2.Int(settingsMap["alerting_timeout_sec"].(int)),
ServiceLevel: &platformclientv2.Servicelevel{
Percentage: platformclientv2.Float64(settingsMap["service_level_percentage"].(float64)),
DurationMs: platformclientv2.Int(settingsMap["service_level_duration_ms"].(int)),
},
EnableAutoAnswer: platformclientv2.Bool(settingsMap["enable_auto_answer"].(bool)),
AutoEndDelaySeconds: platformclientv2.Int(settingsMap["auto_end_delay_seconds"].(int)),
AutoDialDelaySeconds: platformclientv2.Int(settingsMap["auto_dial_delay_seconds"].(int)),
EnableAutoDialAndEnd: platformclientv2.Bool(settingsMap["enable_auto_dial_and_end"].(bool)),
}
}
func flattenAgentOwnedRouting(settings *platformclientv2.Agentownedrouting) []interface{} {
settingsMap := make(map[string]interface{})
settingsMap["max_owned_callback_delay_hours"] = *settings.MaxOwnedCallbackDelayHours
resourcedata.SetMapValueIfNotNil(settingsMap, "enable_agent_owned_callbacks", settings.EnableAgentOwnedCallbacks)
settingsMap["max_owned_callback_hours"] = *settings.MaxOwnedCallbackHours
return []interface{}{settingsMap}
}
func flattenMediaSetting(settings *platformclientv2.Mediasettings) []interface{} {
settingsMap := make(map[string]interface{})
settingsMap["alerting_timeout_sec"] = *settings.AlertingTimeoutSeconds
resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_answer", settings.EnableAutoAnswer)
settingsMap["service_level_percentage"] = *settings.ServiceLevel.Percentage
settingsMap["service_level_duration_ms"] = *settings.ServiceLevel.DurationMs
return []interface{}{settingsMap}
}
func flattenMediaSettingCallback(settings *platformclientv2.Callbackmediasettings) []interface{} {
settingsMap := make(map[string]interface{})
settingsMap["alerting_timeout_sec"] = *settings.AlertingTimeoutSeconds
settingsMap["service_level_percentage"] = *settings.ServiceLevel.Percentage
settingsMap["service_level_duration_ms"] = *settings.ServiceLevel.DurationMs
resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_answer", settings.EnableAutoAnswer)
resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_dial_and_end", settings.EnableAutoDialAndEnd)
settingsMap["auto_end_delay_seconds"] = *settings.AutoEndDelaySeconds
settingsMap["auto_dial_delay_seconds"] = *settings.AutoDialDelaySeconds
return []interface{}{settingsMap}
}
func buildMemberGroupList(d *schema.ResourceData, groupKey string, groupType string) *[]platformclientv2.Membergroup {
var memberGroups []platformclientv2.Membergroup
if mg, ok := d.GetOk(groupKey); ok {
for _, mgId := range mg.(*schema.Set).List() {
id := mgId.(string)
memberGroup := &platformclientv2.Membergroup{Id: &id, VarType: &groupType}
memberGroups = append(memberGroups, *memberGroup)
}
}
return &memberGroups
}
func buildSdkRoutingRules(d *schema.ResourceData) *[]platformclientv2.Routingrule {
var routingRules []platformclientv2.Routingrule
if configRoutingRules, ok := d.GetOk("routing_rules"); ok {
for _, configRule := range configRoutingRules.([]interface{}) {
ruleSettings, ok := configRule.(map[string]interface{})
if !ok {
continue
}
var sdkRule platformclientv2.Routingrule
resourcedata.BuildSDKStringValueIfNotNil(&sdkRule.Operator, ruleSettings, "operator")
if threshold, ok := ruleSettings["threshold"]; ok {
v := threshold.(int)
sdkRule.Threshold = &v
}
if waitSeconds, ok := ruleSettings["wait_seconds"].(float64); ok {
sdkRule.WaitSeconds = &waitSeconds
}
routingRules = append(routingRules, sdkRule)
}
}
return &routingRules
}
func flattenRoutingRules(sdkRoutingRules *[]platformclientv2.Routingrule) []interface{} {
rules := make([]interface{}, len(*sdkRoutingRules))
for i, sdkRule := range *sdkRoutingRules {
ruleSettings := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(ruleSettings, "operator", sdkRule.Operator)
resourcedata.SetMapValueIfNotNil(ruleSettings, "threshold", sdkRule.Threshold)
resourcedata.SetMapValueIfNotNil(ruleSettings, "wait_seconds", sdkRule.WaitSeconds)
rules[i] = ruleSettings
}
return rules
}
func buildSdkBullseyeSettings(d *schema.ResourceData) *platformclientv2.Bullseye {
if configRings, ok := d.GetOk("bullseye_rings"); ok {
var sdkRings []platformclientv2.Ring
for _, configRing := range configRings.([]interface{}) {
ringSettings, ok := configRing.(map[string]interface{})
if !ok {
continue
}
var sdkRing platformclientv2.Ring
if waitSeconds, ok := ringSettings["expansion_timeout_seconds"].(float64); ok {
sdkRing.ExpansionCriteria = &[]platformclientv2.Expansioncriterium{
{
VarType: &bullseyeExpansionTypeTimeout,
Threshold: &waitSeconds,
},
}
}
if skillsToRemove, ok := ringSettings["skills_to_remove"]; ok {
skillIds := skillsToRemove.(*schema.Set).List()
if len(skillIds) > 0 {
sdkSkillsToRemove := make([]platformclientv2.Skillstoremove, len(skillIds))
for i, id := range skillIds {
skillID := id.(string)
sdkSkillsToRemove[i] = platformclientv2.Skillstoremove{
Id: &skillID,
}
}
sdkRing.Actions = &platformclientv2.Actions{
SkillsToRemove: &sdkSkillsToRemove,
}
}
}
if memberGroups, ok := ringSettings["member_groups"]; ok {
memberGroupList := memberGroups.(*schema.Set).List()
if len(memberGroupList) > 0 {
sdkMemberGroups := make([]platformclientv2.Membergroup, len(memberGroupList))
for i, memberGroup := range memberGroupList {
settingsMap := memberGroup.(map[string]interface{})
memberGroupID := settingsMap["member_group_id"].(string)
memberGroupType := settingsMap["member_group_type"].(string)
sdkMemberGroups[i] = platformclientv2.Membergroup{
Id: &memberGroupID,
VarType: &memberGroupType,
}
}
sdkRing.MemberGroups = &sdkMemberGroups
}
}
sdkRings = append(sdkRings, sdkRing)
}
/*
The routing queues API is a little unusual. You can have up to six bullseye routing rings but the last one is always
a treated as the default ring. This means you can actually ony define a maximum of 5. So, I have changed the behavior of this
resource to only allow you to add 5 items and then the code always adds a 6 item (see the code below) with a default timeout of 2.
*/
var defaultSdkRing platformclientv2.Ring
defaultTimeoutInt := 2
defaultTimeoutFloat := float64(defaultTimeoutInt)
defaultSdkRing.ExpansionCriteria = &[]platformclientv2.Expansioncriterium{
{
VarType: &bullseyeExpansionTypeTimeout,
Threshold: &defaultTimeoutFloat,
},
}
sdkRings = append(sdkRings, defaultSdkRing)
return &platformclientv2.Bullseye{Rings: &sdkRings}
}
return nil
}
/*
The flattenBullseyeRings function maps the data retrieved from our SDK call over to the bullseye_ring attribute within the provider.
You might notice in the code that we are always mapping all but the last item in the list of rings retrieved by the API. The reason for this
is that when you submit a list of bullseye_rings to the API, the API will always take the last item in the list and use it to drive default behavior
This is a change from earlier parts of the API where you could define 6 bullseye rings and there would always be six. Now when you define bullseye rings,
the public API will take the list item in the list and make it the default and it will not show up on the screen. To get around this you needed
to always add a dumb bullseye ring block. Now, we automatically add one for you. We only except a maximum of 5 bullseyes_ring blocks, but we will always
remove the last block returned by the API.
*/
func flattenBullseyeRings(sdkRings *[]platformclientv2.Ring) []interface{} {
rings := make([]interface{}, len(*sdkRings)-1) //Sizing the target array of Rings to account for us removing the default block
for i, sdkRing := range *sdkRings {
if i < len(*sdkRings)-1 { //Checking to make sure we are do nothing with the last item in the list by skipping processing if it is defined
ringSettings := make(map[string]interface{})
if sdkRing.ExpansionCriteria != nil {
for _, criteria := range *sdkRing.ExpansionCriteria {
if *criteria.VarType == bullseyeExpansionTypeTimeout {
ringSettings["expansion_timeout_seconds"] = *criteria.Threshold
break
}
}
}
if sdkRing.Actions != nil && sdkRing.Actions.SkillsToRemove != nil {
skillIds := make([]interface{}, len(*sdkRing.Actions.SkillsToRemove))
for s, skill := range *sdkRing.Actions.SkillsToRemove {
skillIds[s] = *skill.Id
}
ringSettings["skills_to_remove"] = schema.NewSet(schema.HashString, skillIds)
}
if sdkRing.MemberGroups != nil {
memberGroups := schema.NewSet(schema.HashResource(memberGroupResource), []interface{}{})
for _, memberGroup := range *sdkRing.MemberGroups {
memberGroupMap := make(map[string]interface{})
memberGroupMap["member_group_id"] = *memberGroup.Id
memberGroupMap["member_group_type"] = *memberGroup.VarType
memberGroups.Add(memberGroupMap)
}
ringSettings["member_groups"] = memberGroups
}
rings[i] = ringSettings
}
}
return rings
}
func buildSdkConditionalGroupRouting(d *schema.ResourceData) (*platformclientv2.Conditionalgrouprouting, diag.Diagnostics) {
if configRules, ok := d.GetOk("conditional_group_routing_rules"); ok {
var sdkCGRRules []platformclientv2.Conditionalgrouproutingrule
for i, configRules := range configRules.([]interface{}) {
ruleSettings, ok := configRules.(map[string]interface{})
if !ok {
continue
}
var sdkCGRRule platformclientv2.Conditionalgrouproutingrule
if waitSeconds, ok := ruleSettings["wait_seconds"].(int); ok {
sdkCGRRule.WaitSeconds = &waitSeconds
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkCGRRule.Operator, ruleSettings, "operator")
if conditionValue, ok := ruleSettings["condition_value"].(float64); ok {
sdkCGRRule.ConditionValue = &conditionValue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkCGRRule.Metric, ruleSettings, "metric")
if queueId, ok := ruleSettings["queue_id"].(string); ok && queueId != "" {
if i == 0 {
return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("For rule 1, queue_id is always assumed to be the current queue, so queue id should not be specified"), fmt.Errorf("queue id is not nil"))
}
sdkCGRRule.Queue = &platformclientv2.Domainentityref{Id: &queueId}
}
if memberGroupList, ok := ruleSettings["groups"].([]interface{}); ok {
if len(memberGroupList) > 0 {
sdkMemberGroups := make([]platformclientv2.Membergroup, len(memberGroupList))
for i, memberGroup := range memberGroupList {
settingsMap, ok := memberGroup.(map[string]interface{})
if !ok {
continue
}
sdkMemberGroups[i] = platformclientv2.Membergroup{
Id: platformclientv2.String(settingsMap["member_group_id"].(string)),
VarType: platformclientv2.String(settingsMap["member_group_type"].(string)),
}
}
sdkCGRRule.Groups = &sdkMemberGroups
}
}
sdkCGRRules = append(sdkCGRRules, sdkCGRRule)
}
rules := &sdkCGRRules
return &platformclientv2.Conditionalgrouprouting{Rules: rules}, nil
}
return nil, nil
}
func flattenConditionalGroupRoutingRules(queue *platformclientv2.Queue) []interface{} {
if queue.ConditionalGroupRouting == nil || len(*queue.ConditionalGroupRouting.Rules) == 0 {
return nil
}
rules := make([]interface{}, len(*queue.ConditionalGroupRouting.Rules))
for i, rule := range *queue.ConditionalGroupRouting.Rules {
ruleSettings := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(ruleSettings, "wait_seconds", rule.WaitSeconds)
resourcedata.SetMapValueIfNotNil(ruleSettings, "operator", rule.Operator)
resourcedata.SetMapValueIfNotNil(ruleSettings, "condition_value", rule.ConditionValue)
resourcedata.SetMapValueIfNotNil(ruleSettings, "metric", rule.Metric)
// The first rule is assumed to apply to this queue, so queue_id should be omitted if the conditional grouping routing rule
//is the first one being looked at.
if rule.Queue != nil && i > 0 {
ruleSettings["queue_id"] = *rule.Queue.Id
}
if rule.Groups != nil {
memberGroups := make([]interface{}, 0)
for _, group := range *rule.Groups {
memberGroupMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_id", group.Id)
resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_type", group.VarType)
memberGroups = append(memberGroups, memberGroupMap)
}
ruleSettings["groups"] = memberGroups
}
rules[i] = ruleSettings
}
return rules
}
func buildSdkAcwSettings(d *schema.ResourceData) *platformclientv2.Acwsettings {
acwWrapupPrompt := d.Get("acw_wrapup_prompt").(string)
acwSettings := platformclientv2.Acwsettings{
WrapupPrompt: &acwWrapupPrompt, // Set or default
}
// Only set timeout for certain wrapup prompt types
if acwWrapupPrompt == "MANDATORY_TIMEOUT" || acwWrapupPrompt == "MANDATORY_FORCED_TIMEOUT" || acwWrapupPrompt == "AGENT_REQUESTED" {
acwTimeoutMs, hasTimeout := d.GetOk("acw_timeout_ms")
if hasTimeout {
timeout := acwTimeoutMs.(int)
acwSettings.TimeoutMs = &timeout
}
}
return &acwSettings
}
func buildSdkDefaultScriptsMap(d *schema.ResourceData) *map[string]platformclientv2.Script {
if scriptIds, ok := d.GetOk("default_script_ids"); ok {
scriptMap := scriptIds.(map[string]interface{})
results := make(map[string]platformclientv2.Script)
for k, v := range scriptMap {
scriptID := v.(string)
results[k] = platformclientv2.Script{Id: &scriptID}
}
return &results
}
return nil
}
func flattenDefaultScripts(sdkScripts map[string]platformclientv2.Script) map[string]interface{} {
if len(sdkScripts) == 0 {
return nil
}
results := make(map[string]interface{})
for k, v := range sdkScripts {
results[k] = *v.Id
}
return results
}
func validateMapCommTypes(val interface{}, _ cty.Path) diag.Diagnostics {
if val == nil {
return nil
}
commTypes := []string{"CALL", "CALLBACK", "CHAT", "COBROWSE", "EMAIL", "MESSAGE", "SOCIAL_EXPRESSION", "VIDEO", "SCREENSHARE"}
m := val.(map[string]interface{})
for k := range m {
if !lists.ItemInSlice(k, commTypes) {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("%s is an invalid communication type key.", k), fmt.Errorf("invalid communication type key"))
}
}
return nil
}
func buildSdkQueueMessagingAddresses(d *schema.ResourceData) *platformclientv2.Queuemessagingaddresses {
if _, ok := d.GetOk("outbound_messaging_sms_address_id"); ok {
return &platformclientv2.Queuemessagingaddresses{
SmsAddress: util.BuildSdkDomainEntityRef(d, "outbound_messaging_sms_address_id"),
}
}
return nil
}
func buildSdkQueueEmailAddress(d *schema.ResourceData) *platformclientv2.Queueemailaddress {
outboundEmailAddress := d.Get("outbound_email_address").([]interface{})
if outboundEmailAddress != nil && len(outboundEmailAddress) > 0 {
settingsMap := outboundEmailAddress[0].(map[string]interface{})
inboundRoute := &platformclientv2.Inboundroute{
Id: platformclientv2.String(settingsMap["route_id"].(string)),
}
return &platformclientv2.Queueemailaddress{
Domain: &platformclientv2.Domainentityref{Id: platformclientv2.String(settingsMap["domain_id"].(string))},
Route: &inboundRoute,
}
}
return nil
}
func FlattenQueueEmailAddress(settings platformclientv2.Queueemailaddress) map[string]interface{} {
settingsMap := make(map[string]interface{})
resourcedata.SetMapReferenceValueIfNotNil(settingsMap, "domain_id", settings.Domain)
if settings.Route != nil {
route := *settings.Route
settingsMap["route_id"] = *route.Id
}
return settingsMap
}
func buildSdkDirectRouting(d *schema.ResourceData) *platformclientv2.Directrouting {
directRouting := d.Get("direct_routing").([]interface{})
if directRouting != nil && len(directRouting) > 0 {
settingsMap := directRouting[0].(map[string]interface{})
agentWaitSeconds := settingsMap["agent_wait_seconds"].(int)
waitForAgent := settingsMap["wait_for_agent"].(bool)
callUseAgentAddressOutbound := settingsMap["call_use_agent_address_outbound"].(bool)
callSettings := &platformclientv2.Directroutingmediasettings{
UseAgentAddressOutbound: &callUseAgentAddressOutbound,
}
emailUseAgentAddressOutbound := settingsMap["email_use_agent_address_outbound"].(bool)
emailSettings := &platformclientv2.Directroutingmediasettings{
UseAgentAddressOutbound: &emailUseAgentAddressOutbound,
}
messageUseAgentAddressOutbound := settingsMap["message_use_agent_address_outbound"].(bool)
messageSettings := &platformclientv2.Directroutingmediasettings{
UseAgentAddressOutbound: &messageUseAgentAddressOutbound,
}
sdkDirectRouting := &platformclientv2.Directrouting{
CallMediaSettings: callSettings,
EmailMediaSettings: emailSettings,
MessageMediaSettings: messageSettings,
WaitForAgent: &waitForAgent,
AgentWaitSeconds: &agentWaitSeconds,
}
if backUpQueueId, ok := settingsMap["backup_queue_id"].(string); ok && backUpQueueId != "" {
sdkDirectRouting.BackupQueueId = &backUpQueueId
}
return sdkDirectRouting
}
return nil
}
func flattenDirectRouting(settings *platformclientv2.Directrouting) []interface{} {
settingsMap := make(map[string]interface{})
if settings.BackupQueueId != nil {
settingsMap["backup_queue_id"] = *settings.BackupQueueId
}
if settings.AgentWaitSeconds != nil {
settingsMap["agent_wait_seconds"] = *settings.AgentWaitSeconds
}
if settings.WaitForAgent != nil {
settingsMap["wait_for_agent"] = *settings.WaitForAgent
}
if settings.CallMediaSettings != nil {
callSettings := *settings.CallMediaSettings
settingsMap["call_use_agent_address_outbound"] = *callSettings.UseAgentAddressOutbound
}
if settings.EmailMediaSettings != nil {
emailSettings := *settings.EmailMediaSettings
settingsMap["email_use_agent_address_outbound"] = *emailSettings.UseAgentAddressOutbound
}
if settings.MessageMediaSettings != nil {
messageSettings := *settings.MessageMediaSettings
settingsMap["message_use_agent_address_outbound"] = *messageSettings.UseAgentAddressOutbound
}
return []interface{}{settingsMap}
}
func updateQueueWrapupCodes(d *schema.ResourceData, routingAPI *platformclientv2.RoutingApi) diag.Diagnostics {
if d.HasChange("wrapup_codes") {
if codesConfig := d.Get("wrapup_codes"); codesConfig != nil {
// Get existing codes
codes, err := getRoutingQueueWrapupCodes(d.Id(), routingAPI)
if err != nil {
return err
}
var existingCodes []string
if codes != nil {
for _, code := range codes {
existingCodes = append(existingCodes, *code.Id)
}
}
configCodes := *lists.SetToStringList(codesConfig.(*schema.Set))
codesToRemove := lists.SliceDifference(existingCodes, configCodes)
if len(codesToRemove) > 0 {
for _, codeId := range codesToRemove {
resp, err := routingAPI.DeleteRoutingQueueWrapupcode(d.Id(), codeId)
if err != nil {
if util.IsStatus404(resp) {
// Ignore missing queue or wrapup code
continue
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to remove wrapup codes for queue %s error: %s", d.Id(), err), resp)
}
}
}
codesToAdd := lists.SliceDifference(configCodes, existingCodes)
if len(codesToAdd) > 0 {
err := addWrapupCodesInChunks(d.Id(), codesToAdd, routingAPI)
if err != nil {
return err
}
}
}
}
return nil
}
func addWrapupCodesInChunks(queueID string, codesToAdd []string, api *platformclientv2.RoutingApi) diag.Diagnostics {
// API restricts wrapup code adds to 100 per call
const maxBatchSize = 100
for i := 0; i < len(codesToAdd); i += maxBatchSize {
end := i + maxBatchSize
if end > len(codesToAdd) {
end = len(codesToAdd)
}
var updateChunk []platformclientv2.Wrapupcodereference
for j := i; j < end; j++ {
updateChunk = append(updateChunk, platformclientv2.Wrapupcodereference{Id: &codesToAdd[j]})
}
if len(updateChunk) > 0 {
_, resp, err := api.PostRoutingQueueWrapupcodes(queueID, updateChunk)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update wrapup codes for queue %s error: %s", queueID, err), resp)
}
}
}
return nil
}
func getRoutingQueueWrapupCodes(queueID string, api *platformclientv2.RoutingApi) ([]platformclientv2.Wrapupcode, diag.Diagnostics) {
const maxPageSize = 100
var codes []platformclientv2.Wrapupcode
for pageNum := 1; ; pageNum++ {
codeResult, resp, err := api.GetRoutingQueueWrapupcodes(queueID, maxPageSize, pageNum)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to query wrapup codes for queue %s error: %s", queueID, err), resp)
}
if codeResult == nil || codeResult.Entities == nil || len(*codeResult.Entities) == 0 {
return codes, nil
}
for _, code := range *codeResult.Entities {
codes = append(codes, code)
}
}
}
func updateQueueMembers(d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
if !d.HasChange("members") {
return nil
}
membersSet, ok := d.Get("members").(*schema.Set)
if !ok || membersSet.Len() == 0 {
if err := removeAllExistingUserMembersFromQueue(d.Id(), sdkConfig); err != nil {
return diag.FromErr(err)
}
return nil
}
log.Printf("Updating members for Queue %s", d.Get("name"))
newUserRingNums := make(map[string]int)
memberList := membersSet.List()
newUserIds := make([]string, len(memberList))
for i, member := range memberList {
memberMap := member.(map[string]interface{})
newUserIds[i] = memberMap["user_id"].(string)
newUserRingNums[newUserIds[i]] = memberMap["ring_num"].(int)
}
if len(newUserIds) > 0 {
log.Printf("Sleeping for 10 seconds")
time.Sleep(10 * time.Second)
for _, userId := range newUserIds {
if err := verifyUserIsNotGroupMemberOfQueue(d.Id(), userId, sdkConfig); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error verifying user %s is not group member of queue", d.Id()), err)
}
}
}
oldSdkUsers, err := getRoutingQueueMembers(d.Id(), "user", sdkConfig)
if err != nil {
return err
}
oldUserIds := make([]string, len(oldSdkUsers))
oldUserRingNums := make(map[string]int)
for i, user := range oldSdkUsers {
oldUserIds[i] = *user.Id
oldUserRingNums[oldUserIds[i]] = *user.RingNumber
}
if len(oldUserIds) > 0 {
usersToRemove := lists.SliceDifference(oldUserIds, newUserIds)
err := updateMembersInChunks(d.Id(), usersToRemove, true, sdkConfig)
if err != nil {
return err
}
}
if len(newUserIds) > 0 {
usersToAdd := lists.SliceDifference(newUserIds, oldUserIds)
err := updateMembersInChunks(d.Id(), usersToAdd, false, sdkConfig)
if err != nil {
return err
}
}
// Check for ring numbers to update
for userID, newNum := range newUserRingNums {
if oldNum, found := oldUserRingNums[userID]; found {
if newNum != oldNum {
log.Printf("updating ring_num for user %s because it has updated. New: %v, Old: %v", userID, newNum, oldNum)
// Number changed. Update ring number
err := updateQueueUserRingNum(d.Id(), userID, newNum, sdkConfig)
if err != nil {
return err
}
}
} else if newNum != 1 {
// New queue member. Update ring num if not set to the default of 1
log.Printf("updating user %s ring_num because it is not the default 1", userID)
err := updateQueueUserRingNum(d.Id(), userID, newNum, sdkConfig)
if err != nil {
return err
}
}
}
log.Printf("Members updated for Queue %s", d.Get("name"))
return nil
}
// removeAllExistingUserMembersFromQueue get all existing user members of a given queue and remove them from the queue
func removeAllExistingUserMembersFromQueue(queueId string, sdkConfig *platformclientv2.Configuration) error {
log.Printf("Reading user members of queue %s", queueId)
oldSdkUsers, err := getRoutingQueueMembers(queueId, "user", sdkConfig)
if err != nil {
return fmt.Errorf("%v", err)
}
log.Printf("Read user members of queue %s", queueId)
var oldUserIds []string
for _, user := range oldSdkUsers {
oldUserIds = append(oldUserIds, *user.Id)
}
if len(oldUserIds) > 0 {
log.Printf("Removing queue %s user members", queueId)
if err := updateMembersInChunks(queueId, oldUserIds, true, sdkConfig); err != nil {
return fmt.Errorf("%v", err)
}
log.Printf("Removing queue %s user members", queueId)
}
return nil
}
// verifyUserIsNotGroupMemberOfQueue Search through queue group members to verify that a given user is not a group member
func verifyUserIsNotGroupMemberOfQueue(queueId, userId string, sdkConfig *platformclientv2.Configuration) error {
var (
userName string
routingApi = platformclientv2.NewRoutingApiWithConfig(sdkConfig)
usersApi = platformclientv2.NewUsersApiWithConfig(sdkConfig)
)
log.Printf("verifying that member '%s' is not assinged to the queue '%s' via a group", userId, queueId)
// Read name of user to filter results when listing members of queue
log.Printf("reading user %s to fetch name", userId)
user, _, err := usersApi.GetUser(userId, nil, "", "")
if err != nil {
log.Printf("Failed to read name of user '%s' inside verifyUserIsNotGroupMemberOfQueue: %s. Queue ID: %s", userId, err, queueId)
} else {
userName = *user.Name
log.Printf("read user %s %s", userId, userName)
}
const pageSize = 100
for pageNum := 1; ; pageNum++ {
users, resp, err := sdkGetRoutingQueueMembers(queueId, "group", userName, pageNum, pageSize, routingApi)
if err != nil || resp.StatusCode != http.StatusOK {
log.Printf("Error requesting group members of queue '%s': %v. Cannot validate that user '%s' is not already assigned via a group", queueId, err, userId)
break
}
if users == nil || users.Entities == nil || len(*users.Entities) == 0 {
break
}
for _, member := range *users.Entities {
if userId == *member.Id {
return fmt.Errorf("member %s '%s' is already assigned to queue '%s' via a group, and cannot be assigned using the members set", userName, userId, queueId)
}
}
}
log.Printf("User %s not found as group member in queue %s", userId, queueId)
return nil
}
func updateMembersInChunks(queueID string, membersToUpdate []string, remove bool, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
api := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
// API restricts member adds/removes to 100 per call
// Generic call to prepare chunks for the Update. Takes in three args
// 1. MemberstoUpdate 2. The Entity prepare func for the update 3. Chunk Size
if len(membersToUpdate) > 0 {
chunks := chunksProcess.ChunkItems(membersToUpdate, platformWritableEntityFunc, 100)
// Closure to process the chunks
chunkProcessor := func(chunk []platformclientv2.Writableentity) diag.Diagnostics {
resp, err := api.PostRoutingQueueMembers(queueID, chunk, remove)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update members in queue %s error: %s", queueID, err), resp)
}
return nil
}
// Generic Function call which takes in the chunks and the processing function
return chunksProcess.ProcessChunks(chunks, chunkProcessor)
}
return nil
}
func platformWritableEntityFunc(val string) platformclientv2.Writableentity {
return platformclientv2.Writableentity{Id: &val}
}
func updateQueueUserRingNum(queueID string, userID string, ringNum int, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
api := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
resp, err := api.PatchRoutingQueueMember(queueID, userID, platformclientv2.Queuemember{
Id: &userID,
RingNumber: &ringNum,
})
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update ring number for queue %s user %s error: %s", queueID, userID, err), resp)
}
return nil
}
func getRoutingQueueMembers(queueID string, memberBy string, sdkConfig *platformclientv2.Configuration) ([]platformclientv2.Queuemember, diag.Diagnostics) {
var members []platformclientv2.Queuemember
const pageSize = 100
api := platformclientv2.NewRoutingApiWithConfig(sdkConfig)
// Need to call this method to find the member count for a queue. GetRoutingQueueMembers does not return a `total` property for us to use.
queue, resp, err := api.GetRoutingQueue(queueID)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to find queue %s error: %s", queueID, err), resp)
}
queueMembers := *queue.MemberCount
log.Printf("%d members belong to queue %s", queueMembers, queueID)
for pageNum := 1; ; pageNum++ {
users, resp, err := sdkGetRoutingQueueMembers(queueID, memberBy, "", pageNum, pageSize, api)
if err != nil || resp.StatusCode != http.StatusOK {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to query users for queue %s error: %s", queueID, err), resp)
}
if users == nil || users.Entities == nil || len(*users.Entities) == 0 {
membersFound := len(members)
log.Printf("%d queue members found for queue %s", membersFound, queueID)
if membersFound != queueMembers {
log.Printf("Member count is not equal to queue member found for queue %s, Correlation Id: %s", queueID, resp.CorrelationID)
}
return members, nil
}
for _, user := range *users.Entities {
members = append(members, user)
}
}
}
func sdkGetRoutingQueueMembers(queueID, memberBy, name string, pageNumber, pageSize int, api *platformclientv2.RoutingApi) (*platformclientv2.Queuememberentitylisting, *platformclientv2.APIResponse, error) {
// SDK does not support nil values for boolean query params yet, so we must manually construct this HTTP request for now
apiClient := &api.Configuration.APIClient
// create path and map variables
path := api.Configuration.BasePath + "/api/v2/routing/queues/{queueId}/members"
path = strings.Replace(path, "{queueId}", queueID, -1)
headerParams := make(map[string]string)
queryParams := make(map[string]string)
formParams := url.Values{}
var postBody interface{}
var postFileName string
var fileBytes []byte
// oauth required
if api.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken
}
// add default headers if any
for key := range api.Configuration.DefaultHeader {
headerParams[key] = api.Configuration.DefaultHeader[key]
}
queryParams["pageSize"] = apiClient.ParameterToString(pageSize, "")
queryParams["pageNumber"] = apiClient.ParameterToString(pageNumber, "")
if memberBy != "" {
queryParams["memberBy"] = memberBy
}
if name != "" {
queryParams["name"] = name
}
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload *platformclientv2.Queuememberentitylisting
response, err := apiClient.CallAPI(path, http.MethodGet, postBody, headerParams, queryParams, formParams, postFileName, fileBytes)
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = fmt.Errorf(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
}
return successPayload, response, err
}
func flattenQueueMembers(queueID string, memberBy string, sdkConfig *platformclientv2.Configuration) (*schema.Set, diag.Diagnostics) {
members, err := getRoutingQueueMembers(queueID, memberBy, sdkConfig)
if err != nil {
return nil, err
}
memberSet := schema.NewSet(schema.HashResource(queueMemberResource), []interface{}{})
for _, member := range members {
memberMap := make(map[string]interface{})
memberMap["user_id"] = *member.Id
memberMap["ring_num"] = *member.RingNumber
memberSet.Add(memberMap)
}
return memberSet, nil
}
func flattenQueueWrapupCodes(ctx context.Context, queueID string, proxy *RoutingQueueProxy) (*schema.Set, diag.Diagnostics) {
codeIds, resp, err := proxy.getRoutingQueueWrapupCodeIds(ctx, queueID)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to query wrapup codes for queue %s", queueID), resp)
}
if codeIds != nil {
return lists.StringListToSet(codeIds), nil
}
return nil, nil
}
func flattenQueueMemberGroupsList(queue *platformclientv2.Queue, groupType *string) *schema.Set {
var groupIds []string
if queue == nil || queue.MemberGroups == nil {
return nil
}
for _, memberGroup := range *queue.MemberGroups {
if strings.Compare(*memberGroup.VarType, *groupType) == 0 {
groupIds = append(groupIds, *memberGroup.Id)
}
}
if groupIds != nil {
return lists.StringListToSet(groupIds)
}
return nil
}
package routing_queue
import (
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
const resourceName = "genesyscloud_routing_queue"
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceRoutingQueue())
regInstance.RegisterDataSource(resourceName, DataSourceRoutingQueue())
regInstance.RegisterExporter(resourceName, RoutingQueueExporter())
}
var (
memberGroupResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"member_group_id": {
Description: "ID (GUID) for Group, SkillGroup, Team",
Type: schema.TypeString,
Required: true,
},
"member_group_type": {
Description: "The type of the member group. Accepted values: TEAM, GROUP, SKILLGROUP",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"TEAM", "GROUP", "SKILLGROUP"}, false),
},
},
}
agentOwnedRoutingResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"enable_agent_owned_callbacks": {
Description: "Enable Agent Owned Callbacks",
Type: schema.TypeBool,
Required: true,
},
"max_owned_callback_hours": {
Description: "Auto End Delay Seconds Must be >= 7",
Type: schema.TypeInt,
Required: true,
},
"max_owned_callback_delay_hours": {
Description: "Max Owned Call Back Delay Hours >= 7",
Type: schema.TypeInt,
Required: true,
},
},
}
queueMediaSettingsResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"alerting_timeout_sec": {
Description: "Alerting timeout in seconds. Must be >= 7",
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(7),
},
"auto_end_delay_seconds": {
Description: "Auto End Delay Seconds.",
Type: schema.TypeInt,
Optional: true,
},
"auto_dial_delay_seconds": {
Description: "Auto Dial Delay Seconds.",
Type: schema.TypeInt,
Optional: true,
},
"enable_auto_answer": {
Description: "Auto-Answer for digital channels(Email, Message)",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"enable_auto_dial_and_end": {
Description: "Auto Dail and End",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"service_level_percentage": {
Description: "The desired Service Level. A float value between 0 and 1.",
Type: schema.TypeFloat,
Required: true,
ValidateFunc: validation.FloatBetween(0, 1),
},
"service_level_duration_ms": {
Description: "Service Level target in milliseconds. Must be >= 1000",
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1000),
},
},
}
queueMemberResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"user_id": {
Description: "User ID",
Type: schema.TypeString,
Required: true,
},
"ring_num": {
Description: "Ring number between 1 and 6 for this user in the queue.",
Type: schema.TypeInt,
Optional: true,
Default: 1,
ValidateFunc: validation.IntBetween(1, 6),
},
},
}
directRoutingResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"backup_queue_id": {
Description: "Direct Routing default backup queue id (if none supplied this queue will be used as backup).",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"agent_wait_seconds": {
Description: "The queue default time a Direct Routing interaction will wait for an agent before it goes to configured backup.",
Type: schema.TypeInt,
Optional: true,
Default: 60,
},
"wait_for_agent": {
Description: "Boolean indicating if Direct Routing interactions should wait for the targeted agent by default.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"call_use_agent_address_outbound": {
Description: "Boolean indicating if user Direct Routing addresses should be used outbound on behalf of queue in place of Queue address for calls.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"email_use_agent_address_outbound": {
Description: "Boolean indicating if user Direct Routing addresses should be used outbound on behalf of queue in place of Queue address for emails.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"message_use_agent_address_outbound": {
Description: "Boolean indicating if user Direct Routing addresses should be used outbound on behalf of queue in place of Queue address for messages.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}
)
func ResourceRoutingQueue() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Routing Queue",
CreateContext: provider.CreateWithPooledClient(createQueue),
ReadContext: provider.ReadWithPooledClient(readQueue),
UpdateContext: provider.UpdateWithPooledClient(updateQueue),
DeleteContext: provider.DeleteWithPooledClient(deleteQueue),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Queue name.",
Type: schema.TypeString,
Required: true,
},
"division_id": {
Description: "The division to which this queue will belong. If not set, the home division will be used.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Description: "Queue description.",
Type: schema.TypeString,
Optional: true,
},
"media_settings_call": {
Description: "Call media settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: queueMediaSettingsResource,
},
"agent_owned_routing": {
Description: "Agent Owned Routing.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: agentOwnedRoutingResource,
},
"media_settings_callback": {
Description: "Callback media settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: queueMediaSettingsResource,
},
"media_settings_chat": {
Description: "Chat media settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: queueMediaSettingsResource,
},
"media_settings_email": {
Description: "Email media settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: queueMediaSettingsResource,
},
"media_settings_message": {
Description: "Message media settings.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: queueMediaSettingsResource,
},
"routing_rules": {
Description: "The routing rules for the queue, used for routing to known or preferred agents.",
Type: schema.TypeList,
Optional: true,
MaxItems: 6,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"operator": {
Description: "Matching operator (MEETS_THRESHOLD | ANY). MEETS_THRESHOLD matches any agent with a score at or above the rule's threshold. ANY matches all specified agents, regardless of score.",
Type: schema.TypeString,
Optional: true,
Default: "MEETS_THRESHOLD",
ValidateFunc: validation.StringInSlice([]string{"MEETS_THRESHOLD", "ANY"}, false),
},
"threshold": {
Description: "Threshold required for routing attempt (generally an agent score). Ignored for operator ANY.",
Type: schema.TypeInt,
Optional: true,
},
"wait_seconds": {
Description: "Seconds to wait in this rule before moving to the next.",
Type: schema.TypeFloat,
Optional: true,
Default: 5,
ValidateFunc: validation.FloatBetween(2, 259200),
},
},
},
},
"bullseye_rings": {
Description: "The bullseye ring settings for the queue.",
Type: schema.TypeList,
Optional: true,
MaxItems: 5,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"expansion_timeout_seconds": {
Description: "Seconds to wait in this ring before moving to the next.",
Type: schema.TypeFloat,
Required: true,
ValidateFunc: validation.FloatBetween(0, 259200),
},
"skills_to_remove": {
Description: "Skill IDs to remove on ring exit.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"member_groups": {
Type: schema.TypeSet,
Optional: true,
Elem: memberGroupResource,
},
},
},
},
"conditional_group_routing_rules": {
Description: "The Conditional Group Routing settings for the queue.",
Type: schema.TypeList,
Optional: true,
MaxItems: 5,
Deprecated: "conditional_group_routing_rules is deprecated in genesyscloud_routing_queue. CGR is now a standalone resource, please set ENABLE_STANDALONE_CGR in your environment variables to enable and use genesyscloud_routing_queue_conditional_group_routing",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"queue_id": {
Type: schema.TypeString,
Optional: true,
Description: `The ID of the queue being evaluated for this rule. For rule 1, this is always be the current queue, so no queue id should be specified for the first rule.`,
},
"operator": {
Description: "The operator that compares the actual value against the condition value. Valid values: GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"GreaterThan", "LessThan", "GreaterThanOrEqualTo", "LessThanOrEqualTo"}, false),
},
"metric": {
Description: "The queue metric being evaluated. Valid values: EstimatedWaitTime, ServiceLevel",
Type: schema.TypeString,
Optional: true,
Default: "EstimatedWaitTime",
},
"condition_value": {
Description: "The limit value, beyond which a rule evaluates as true.",
Type: schema.TypeFloat,
Required: true,
ValidateFunc: validation.FloatBetween(0, 259200),
},
"wait_seconds": {
Description: "The number of seconds to wait in this rule, if it evaluates as true, before evaluating the next rule. For the final rule, this is ignored, so need not be specified.",
Type: schema.TypeInt,
Optional: true,
Default: 2,
ValidateFunc: validation.IntBetween(0, 259200),
},
"groups": {
Type: schema.TypeList,
Required: true,
MinItems: 1,
Description: "The group(s) to activate if the rule evaluates as true.",
Elem: memberGroupResource,
},
},
},
},
"acw_wrapup_prompt": {
Description: "This field controls how the UI prompts the agent for a wrapup (MANDATORY | OPTIONAL | MANDATORY_TIMEOUT | MANDATORY_FORCED_TIMEOUT | AGENT_REQUESTED).",
Type: schema.TypeString,
Optional: true,
Default: "MANDATORY_TIMEOUT",
ValidateFunc: validation.StringInSlice([]string{"MANDATORY", "OPTIONAL", "MANDATORY_TIMEOUT", "MANDATORY_FORCED_TIMEOUT", "AGENT_REQUESTED"}, false),
},
"acw_timeout_ms": {
Description: "The amount of time the agent can stay in ACW. Only set when ACW is MANDATORY_TIMEOUT, MANDATORY_FORCED_TIMEOUT or AGENT_REQUESTED.",
Type: schema.TypeInt,
Optional: true,
Computed: true, // Default may be set by server
ValidateFunc: validation.IntBetween(0, 86400000),
},
"skill_evaluation_method": {
Description: "The skill evaluation method to use when routing conversations (NONE | BEST | ALL).",
Type: schema.TypeString,
Optional: true,
Default: "ALL",
ValidateFunc: validation.StringInSlice([]string{"NONE", "BEST", "ALL"}, false),
},
"queue_flow_id": {
Description: "The in-queue flow ID to use for call conversations waiting in queue.",
Type: schema.TypeString,
Optional: true,
},
"email_in_queue_flow_id": {
Description: "The in-queue flow ID to use for email conversations waiting in queue.",
Type: schema.TypeString,
Optional: true,
},
"message_in_queue_flow_id": {
Description: "The in-queue flow ID to use for message conversations waiting in queue.",
Type: schema.TypeString,
Optional: true,
},
"whisper_prompt_id": {
Description: "The prompt ID used for whisper on the queue, if configured.",
Type: schema.TypeString,
Optional: true,
},
"auto_answer_only": {
Description: "Specifies whether the configured whisper should play for all ACD calls, or only for those which are auto-answered.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"enable_transcription": {
Description: "Indicates whether voice transcription is enabled for this queue.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"suppress_in_queue_call_recording": {
Description: "Indicates whether recording in-queue calls is suppressed for this queue.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"enable_manual_assignment": {
Description: "Indicates whether manual assignment is enabled for this queue.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"calling_party_name": {
Description: "The name to use for caller identification for outbound calls from this queue.",
Type: schema.TypeString,
Optional: true,
},
"calling_party_number": {
Description: "The phone number to use for caller identification for outbound calls from this queue.",
Type: schema.TypeString,
Optional: true,
},
"scoring_method": {
Description: "The Scoring Method for the queue. Defaults to TimestampAndPriority.",
Type: schema.TypeString,
Optional: true,
Default: "TimestampAndPriority",
ValidateFunc: validation.StringInSlice([]string{"TimestampAndPriority", "PriorityOnly"}, false),
},
"default_script_ids": {
Description: "The default script IDs for each communication type. Communication types: (CALL | CALLBACK | CHAT | COBROWSE | EMAIL | MESSAGE | SOCIAL_EXPRESSION | VIDEO | SCREENSHARE)",
Type: schema.TypeMap,
ValidateDiagFunc: validateMapCommTypes,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"outbound_messaging_sms_address_id": {
Description: "The unique ID of the outbound messaging SMS address for the queue.",
Type: schema.TypeString,
Optional: true,
},
"outbound_email_address": {
Description: "The outbound email address settings for this queue.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Deprecated: "outbound_email_address is deprecated in genesyscloud_routing_queue. OEA is now a standalone resource, please set ENABLE_STANDALONE_EMAIL_ADDRESS in your environment variables to enable and use genesyscloud_routing_queue_outbound_email_address",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"domain_id": {
Description: "Unique ID of the email domain. e.g. \"test.example.com\"",
Type: schema.TypeString,
Required: true,
},
"route_id": {
Description: "Unique ID of the email route.",
Type: schema.TypeString,
Required: true,
},
},
},
},
"members": {
Description: "Users in the queue. If not set, this resource will not manage members. If a user is already assigned to this queue via a group, attempting to assign them using this field will cause an error to be thrown.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: queueMemberResource,
},
"wrapup_codes": {
Description: "IDs of wrapup codes assigned to this queue. If not set, this resource will not manage wrapup codes.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"direct_routing": {
Description: "Used by the System to set Direct Routing settings for a system Direct Routing queue.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: directRoutingResource,
},
"skill_groups": {
Description: "List of skill group ids assigned to the queue.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"groups": {
Description: "List of group ids assigned to the queue",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"teams": {
Description: "List of ids assigned to the queue",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
func RoutingQueueExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingQueues),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
"queue_flow_id": {RefType: "genesyscloud_flow"},
"email_in_queue_flow_id": {RefType: "genesyscloud_flow"},
"message_in_queue_flow_id": {RefType: "genesyscloud_flow"},
"whisper_prompt_id": {RefType: "genesyscloud_architect_user_prompt"},
"outbound_messaging_sms_address_id": {}, // Ref type not yet defined
"default_script_ids.*": {RefType: "genesyscloud_script"}, // Ref type not yet defined
"outbound_email_address.route_id": {RefType: "genesyscloud_routing_email_route"},
"outbound_email_address.domain_id": {RefType: "genesyscloud_routing_email_domain"},
"bullseye_rings.skills_to_remove": {RefType: "genesyscloud_routing_skill"},
"members.user_id": {RefType: "genesyscloud_user"},
"wrapup_codes": {RefType: "genesyscloud_routing_wrapupcode"},
"skill_groups": {RefType: "genesyscloud_routing_skill_group"},
"teams": {RefType: "genesyscloud_team"},
"groups": {RefType: "genesyscloud_group"},
"conditional_group_routing_rules.queue_id": {RefType: "genesyscloud_routing_queue"},
},
RemoveIfMissing: map[string][]string{
"outbound_email_address": {"route_id"},
"members": {"user_id"},
},
AllowZeroValues: []string{"bullseye_rings.expansion_timeout_seconds"},
CustomAttributeResolver: map[string]*resourceExporter.RefAttrCustomResolver{
"bullseye_rings.member_groups.member_group_id": {ResolverFunc: resourceExporter.MemberGroupsResolver},
"conditional_group_routing_rules.groups.member_group_id": {ResolverFunc: resourceExporter.MemberGroupsResolver},
},
}
}
func DataSourceRoutingQueue() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Routing Queues. Select a queue by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingQueueRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Queue name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package routing_queue
import (
"fmt"
"strings"
)
func GenerateRoutingQueueResource(
resourceID string,
name string,
desc string,
acwWrapupPrompt string,
acwTimeout string,
skillEvalMethod string,
autoAnswerOnly string,
callingPartyName string,
callingPartyNumber string,
enableTranscription string,
suppressInQueueCallRecording string,
enableManualAssignment string,
scoringMethod string,
nestedBlocks ...string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_queue" "%s" {
name = "%s"
description = "%s"
acw_wrapup_prompt = %s
acw_timeout_ms = %s
skill_evaluation_method = %s
auto_answer_only = %s
calling_party_name = %s
calling_party_number = %s
enable_transcription = %s
scoring_method = %s
suppress_in_queue_call_recording = %s
enable_manual_assignment = %s
%s
}
`, resourceID,
name,
desc,
acwWrapupPrompt,
acwTimeout,
skillEvalMethod,
autoAnswerOnly,
callingPartyName,
callingPartyNumber,
enableTranscription,
scoringMethod,
suppressInQueueCallRecording,
enableManualAssignment,
strings.Join(nestedBlocks, "\n"))
}
func GenerateRoutingQueueResourceBasic(resourceID string, name string, nestedBlocks ...string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_queue" "%s" {
name = "%s"
%s
}
`, resourceID, name, strings.Join(nestedBlocks, "\n"))
}
// GenerateRoutingQueueResourceBasicWithDepends Used when testing skills group dependencies.
func GenerateRoutingQueueResourceBasicWithDepends(resourceID string, dependsOn string, name string, nestedBlocks ...string) string {
return fmt.Sprintf(`resource "genesyscloud_routing_queue" "%s" {
depends_on = [%s]
name = "%s"
%s
}
`, resourceID, dependsOn, name, strings.Join(nestedBlocks, "\n"))
}
func GenerateAgentOwnedRouting(attrName string, enableAgentOwnedCallBacks string, maxOwnedCallBackHours string, maxOwnedCallBackDelayHours string) string {
return fmt.Sprintf(`%s {
enable_agent_owned_callbacks = %s
max_owned_callback_hours = %s
max_owned_callback_delay_hours = %s
}
`, attrName, enableAgentOwnedCallBacks, maxOwnedCallBackHours, maxOwnedCallBackDelayHours)
}
func GenerateMediaSettings(attrName string, alertingTimeout string, enableAutoAnswer string, slPercent string, slDurationMs string) string {
return fmt.Sprintf(`%s {
alerting_timeout_sec = %s
enable_auto_answer = %s
service_level_percentage = %s
service_level_duration_ms = %s
}
`, attrName, alertingTimeout, enableAutoAnswer, slPercent, slDurationMs)
}
func GenerateMediaSettingsCallBack(attrName string, alertingTimeout string, enableAutoAnswer string, slPercent string, slDurationMs string, enableAutoDial string, autoEndDelay string, autoDailDelay string) string {
return fmt.Sprintf(`%s {
alerting_timeout_sec = %s
enable_auto_answer = %s
service_level_percentage = %s
service_level_duration_ms = %s
enable_auto_dial_and_end = %s
auto_end_delay_seconds = %s
auto_dial_delay_seconds = %s
}
`, attrName, alertingTimeout, enableAutoAnswer, slPercent, slDurationMs, enableAutoDial, autoEndDelay, autoDailDelay)
}
func GenerateRoutingRules(operator string, threshold string, waitSeconds string) string {
return fmt.Sprintf(`routing_rules {
operator = "%s"
threshold = %s
wait_seconds = %s
}
`, operator, threshold, waitSeconds)
}
func GenerateDefaultScriptIDs(chat, email string) string {
return fmt.Sprintf(`default_script_ids = {
CHAT = "%s"
EMAIL = "%s"
}`, chat, email)
}
func GenerateBullseyeSettings(expTimeout string, skillsToRemove ...string) string {
return fmt.Sprintf(`bullseye_rings {
expansion_timeout_seconds = %s
skills_to_remove = [%s]
}
`, expTimeout, strings.Join(skillsToRemove, ", "))
}
func GenerateConditionalGroupRoutingRules(queueId, operator, metric, conditionValue, waitSeconds string, nestedBlocks ...string) string {
return fmt.Sprintf(`conditional_group_routing_rules {
queue_id = %s
operator = "%s"
metric = "%s"
condition_value = %s
wait_seconds = %s
%s
}
`, queueId, operator, metric, conditionValue, waitSeconds, strings.Join(nestedBlocks, "\n"))
}
func GenerateConditionalGroupRoutingRuleGroup(groupId, groupType string) string {
return fmt.Sprintf(`groups {
member_group_id = %s
member_group_type = "%s"
}
`, groupId, groupType)
}
func GenerateBullseyeSettingsWithMemberGroup(expTimeout, memberGroupId, memberGroupType string, skillsToRemove ...string) string {
return fmt.Sprintf(`bullseye_rings {
expansion_timeout_seconds = %s
skills_to_remove = [%s]
member_groups {
member_group_id = %s
member_group_type = "%s"
}
}
`, expTimeout, strings.Join(skillsToRemove, ", "), memberGroupId, memberGroupType)
}
func GenerateMemberBlock(userID, ringNum string) string {
return fmt.Sprintf(`members {
user_id = %s
ring_num = %s
}
`, userID, ringNum)
}
func GenerateQueueWrapupCodes(wrapupCodes ...string) string {
return fmt.Sprintf(`
wrapup_codes = [%s]
`, strings.Join(wrapupCodes, ", "))
}
package routing_queue_conditional_group_routing
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue"
)
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *routingQueueConditionalGroupRoutingProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getRoutingQueueConditionRoutingFunc func(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, queueId string) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error)
type updateRoutingQueueConditionRoutingFunc func(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, queueId string, rules *[]platformclientv2.Conditionalgrouproutingrule) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error)
// routingQueueConditionalGroupRoutingProxy contains all of the methods that call genesys cloud APIs.
type routingQueueConditionalGroupRoutingProxy struct {
clientConfig *platformclientv2.Configuration
routingApi *platformclientv2.RoutingApi
getRoutingQueueConditionRoutingAttr getRoutingQueueConditionRoutingFunc
updateRoutingQueueConditionRoutingAttr updateRoutingQueueConditionRoutingFunc
routingQueueProxy *routingQueue.RoutingQueueProxy
}
// newRoutingQueueConditionalGroupRoutingProxy initializes the Routing queue conditional group routing proxy with all of the data needed to communicate with Genesys Cloud
func newRoutingQueueConditionalGroupRoutingProxy(clientConfig *platformclientv2.Configuration) *routingQueueConditionalGroupRoutingProxy {
api := platformclientv2.NewRoutingApiWithConfig(clientConfig)
routingQueueProxy := routingQueue.GetRoutingQueueProxy(clientConfig)
return &routingQueueConditionalGroupRoutingProxy{
clientConfig: clientConfig,
routingApi: api,
getRoutingQueueConditionRoutingAttr: getRoutingQueueConditionRoutingFn,
updateRoutingQueueConditionRoutingAttr: updateRoutingQueueConditionRoutingFn,
routingQueueProxy: routingQueueProxy,
}
}
// getRoutingQueueConditionalGroupRoutingProxy retrieves all Genesys Cloud Routing queue conditional group routing
func getRoutingQueueConditionalGroupRoutingProxy(clientConfig *platformclientv2.Configuration) *routingQueueConditionalGroupRoutingProxy {
if internalProxy == nil {
internalProxy = newRoutingQueueConditionalGroupRoutingProxy(clientConfig)
}
return internalProxy
}
// getRoutingQueueConditionRouting gets the conditional group routing rules for a queue
func (p *routingQueueConditionalGroupRoutingProxy) getRoutingQueueConditionRouting(ctx context.Context, queueId string) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) {
return p.getRoutingQueueConditionRoutingAttr(ctx, p, queueId)
}
// updateRoutingQueueConditionRouting updates the conditional group routing rules for a queue
func (p *routingQueueConditionalGroupRoutingProxy) updateRoutingQueueConditionRouting(ctx context.Context, queueId string, rules *[]platformclientv2.Conditionalgrouproutingrule) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) {
return p.updateRoutingQueueConditionRoutingAttr(ctx, p, queueId, rules)
}
// getRoutingQueueConditionRoutingFn is an implementation function for getting the conditional group routing rules for a queue
func getRoutingQueueConditionRoutingFn(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, queueId string) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) {
var (
queue *platformclientv2.Queue
resp *platformclientv2.APIResponse
err error
)
queue = rc.GetCacheItem(p.routingQueueProxy.RoutingQueueCache, queueId)
if queue == nil {
queue, resp, err = p.routingApi.GetRoutingQueue(queueId)
if err != nil {
return nil, resp, fmt.Errorf("error when reading queue %s: %s", queueId, err)
}
}
if queue.ConditionalGroupRouting != nil && queue.ConditionalGroupRouting.Rules != nil {
return queue.ConditionalGroupRouting.Rules, resp, nil
}
return nil, resp, fmt.Errorf("no conditional group routing rules found for queue %s", queueId)
}
// updateRoutingQueueConditionRoutingFn is an implementation function for updating the conditional group routing rules for a queue
func updateRoutingQueueConditionRoutingFn(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, queueId string, rules *[]platformclientv2.Conditionalgrouproutingrule) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) {
// Get the routing queue the rules belong to
queue, resp, err := p.routingApi.GetRoutingQueue(queueId)
if err != nil {
return nil, resp, fmt.Errorf("error when reading queue %s: %s", queueId, err)
}
groupRoutingObj := platformclientv2.Conditionalgrouprouting{Rules: rules}
// Copy over all the values from the original object to the new object
updateQueue := platformclientv2.Queuerequest{
Name: queue.Name,
Description: queue.Description,
MemberCount: queue.MemberCount,
UserMemberCount: queue.UserMemberCount,
JoinedMemberCount: queue.JoinedMemberCount,
MediaSettings: queue.MediaSettings,
RoutingRules: queue.RoutingRules,
ConditionalGroupRouting: &groupRoutingObj, // Add the new rules
Bullseye: queue.Bullseye,
ScoringMethod: queue.ScoringMethod,
AcwSettings: queue.AcwSettings,
SkillEvaluationMethod: queue.SkillEvaluationMethod,
MemberGroups: queue.MemberGroups,
QueueFlow: queue.QueueFlow,
EmailInQueueFlow: queue.EmailInQueueFlow,
MessageInQueueFlow: queue.MessageInQueueFlow,
WhisperPrompt: queue.WhisperPrompt,
OnHoldPrompt: queue.OnHoldPrompt,
AutoAnswerOnly: queue.AutoAnswerOnly,
EnableTranscription: queue.EnableTranscription,
EnableAudioMonitoring: queue.EnableAudioMonitoring,
EnableManualAssignment: queue.EnableManualAssignment,
AgentOwnedRouting: queue.AgentOwnedRouting,
DirectRouting: queue.DirectRouting,
CallingPartyName: queue.CallingPartyName,
CallingPartyNumber: queue.CallingPartyNumber,
DefaultScripts: queue.DefaultScripts,
OutboundMessagingAddresses: queue.OutboundMessagingAddresses,
PeerId: queue.PeerId,
SuppressInQueueCallRecording: queue.SuppressInQueueCallRecording,
}
// For some reason OutboundEmailAddress returned by GetRoutingQueue is a pointer to a pointer so I am handling it here
if queue.OutboundEmailAddress != nil && *queue.OutboundEmailAddress != nil {
updateQueue.OutboundEmailAddress = *queue.OutboundEmailAddress
}
// Update the queue with th new rules
queue, resp, err = p.routingApi.PutRoutingQueue(queueId, updateQueue)
if err != nil {
return nil, resp, fmt.Errorf("failed to update conditional group routing rules for routing queue %s : %s", queueId, err)
}
if queue.ConditionalGroupRouting != nil && queue.ConditionalGroupRouting.Rules != nil {
return queue.ConditionalGroupRouting.Rules, resp, nil
}
return nil, resp, fmt.Errorf("no conditional group routing rules found for queue %s", queueId)
}
package routing_queue_conditional_group_routing
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"strings"
consistencyChecker "terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
featureToggles "terraform-provider-genesyscloud/genesyscloud/util/feature_toggles"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
/*
The resource_genesyscloud_routing_queue_conditional_group_routing.go contains all the methods that perform the core logic for the resource.
*/
func getAllAuthRoutingQueueConditionalGroup(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getRoutingQueueConditionalGroupRoutingProxy(clientConfig)
if exists := featureToggles.CSGToggleExists(); !exists {
log.Printf("Environment variable %s not set, skipping exporter for %s", featureToggles.CSGToggleName(), resourceName)
return nil, nil
}
queues, resp, err := proxy.routingQueueProxy.GetAllRoutingQueues(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to get conditional group routing rules: %s", err), resp)
}
for _, queue := range *queues {
if queue.ConditionalGroupRouting != nil && queue.ConditionalGroupRouting.Rules != nil {
resources[*queue.Id+"/rules"] = &resourceExporter.ResourceMeta{Name: *queue.Name + "-rules"}
}
}
return resources, nil
}
// createRoutingQueueConditionalRoutingGroup is used by the routing_queue_conditional_group_routing resource to create Conditional Group Routing Rules
func createRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if exists := featureToggles.CSGToggleExists(); !exists {
return util.BuildDiagnosticError(resourceName, "Environment variable ENABLE_STANDALONE_CGR not set", fmt.Errorf("environment variable %s not set", featureToggles.CSGToggleName()))
}
queueId := d.Get("queue_id").(string)
log.Printf("creating conditional group routing rules for queue %s", queueId)
d.SetId(queueId + "/rule") // Adding /rule to the id so the id doesn't conflict with the id of the routing queue these rules belong to
return updateRoutingQueueConditionalRoutingGroup(ctx, d, meta)
}
// readRoutingQueueConditionalRoutingGroup is used by the routing_queue_conditional_group_routing resource to read Conditional Group Routing Rules
func readRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if exists := featureToggles.CSGToggleExists(); !exists {
return util.BuildDiagnosticError(resourceName, "Environment variable ENABLE_STANDALONE_CGR not set", fmt.Errorf("environment variable %s not set", featureToggles.CSGToggleName()))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingQueueConditionalGroupRoutingProxy(sdkConfig)
cc := consistencyChecker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingQueueConditionalGroupRouting(), constants.DefaultConsistencyChecks, resourceName)
queueId := strings.Split(d.Id(), "/")[0]
log.Printf("Reading routing queue %s conditional group routing rules", queueId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkRules, resp, getErr := proxy.getRoutingQueueConditionRouting(ctx, queueId)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read conditional group routing for queue %s | error: %s", queueId, getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read conditional group routing for queue %s | error: %s", queueId, getErr), resp))
}
_ = d.Set("queue_id", queueId)
_ = d.Set("rules", flattenConditionalGroupRouting(sdkRules))
return cc.CheckState(d)
})
}
// updateRoutingQueueConditionalRoutingGroup is used by the routing_queue_conditional_group_routing resource to update Conditional Group Routing Rules
func updateRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if exists := featureToggles.CSGToggleExists(); !exists {
return util.BuildDiagnosticError(resourceName, "Environment variable ENABLE_STANDALONE_CGR not set", fmt.Errorf("environment variable %s not set", featureToggles.CSGToggleName()))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingQueueConditionalGroupRoutingProxy(sdkConfig)
queueId := strings.Split(d.Id(), "/")[0]
rules := d.Get("rules").([]interface{})
sdkRules, err := buildConditionalGroupRouting(rules)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error building conditional group routing"), err)
}
log.Printf("updating conditional group routing rules for queue %s", queueId)
_, resp, err := proxy.updateRoutingQueueConditionRouting(ctx, queueId, &sdkRules)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Error updating routing queue conditional routing %s | error: %s", queueId, err), resp)
}
log.Printf("updated conditional group routing rules for queue %s", queueId)
return readRoutingQueueConditionalRoutingGroup(ctx, d, meta)
}
// deleteRoutingQueueConditionalRoutingGroup is used by the routing_queue_conditional_group_routing resource to delete Conditional Group Routing Rules
func deleteRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingQueueConditionalGroupRoutingProxy(sdkConfig)
queueId := strings.Split(d.Id(), "/")[0]
log.Printf("Removing rules from queue %s", queueId)
// check if routing queue still exists before trying to remove rules
_, resp, err := proxy.getRoutingQueueConditionRouting(ctx, queueId)
if err != nil {
if util.IsStatus404(resp) {
log.Printf("conditional group routing rules parent queue %s already deleted", queueId)
return nil
}
}
// To delete conditional group routing, update the queue with no rules
var newRules []platformclientv2.Conditionalgrouproutingrule
_, resp, err = proxy.updateRoutingQueueConditionRouting(ctx, queueId, &newRules)
if err != nil && !strings.Contains(err.Error(), "no conditional group routing rules found for queue") {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to remove rules from queue %s: %s", queueId, err), resp)
}
// Verify there are no rules
rules, resp, err := proxy.getRoutingQueueConditionRouting(ctx, queueId)
if rules != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("conditional group routing rules still exist for queue %s: %s", queueId, err), resp)
}
log.Printf("Removed rules from queue %s", queueId)
return nil
}
func buildConditionalGroupRouting(rules []interface{}) ([]platformclientv2.Conditionalgrouproutingrule, error) {
var sdkRules []platformclientv2.Conditionalgrouproutingrule
for i, rule := range rules {
configRule := rule.(map[string]interface{})
sdkRule := platformclientv2.Conditionalgrouproutingrule{
Operator: platformclientv2.String(configRule["operator"].(string)),
ConditionValue: platformclientv2.Float64(configRule["condition_value"].(float64)),
}
if evaluatedQueue, ok := configRule["evaluated_queue_id"].(string); ok && evaluatedQueue != "" {
if i == 0 {
return nil, fmt.Errorf("for rule 1, the current queue is used so evaluated_queue_id should not be specified")
}
sdkRule.Queue = &platformclientv2.Domainentityref{Id: &evaluatedQueue}
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkRule.Metric, configRule, "metric")
if waitSeconds, ok := configRule["wait_seconds"].(int); ok {
sdkRule.WaitSeconds = &waitSeconds
}
if memberGroupList, ok := configRule["groups"].([]interface{}); ok {
var sdkMemberGroups []platformclientv2.Membergroup
for _, memberGroup := range memberGroupList {
memberGroupMap, ok := memberGroup.(map[string]interface{})
if !ok {
continue
}
sdkMemberGroup := platformclientv2.Membergroup{
Id: platformclientv2.String(memberGroupMap["member_group_id"].(string)),
VarType: platformclientv2.String(memberGroupMap["member_group_type"].(string)),
}
sdkMemberGroups = append(sdkMemberGroups, sdkMemberGroup)
}
sdkRule.Groups = &sdkMemberGroups
}
sdkRules = append(sdkRules, sdkRule)
}
return sdkRules, nil
}
func flattenConditionalGroupRouting(sdkRules *[]platformclientv2.Conditionalgrouproutingrule) []interface{} {
var rules []interface{}
for i, sdkRule := range *sdkRules {
rule := make(map[string]interface{})
// The first rule is assumed to apply to this queue, so evaluated_queue_id should be omitted
if i > 0 {
resourcedata.SetMapReferenceValueIfNotNil(rule, "evaluated_queue_id", sdkRule.Queue)
}
resourcedata.SetMapValueIfNotNil(rule, "wait_seconds", sdkRule.WaitSeconds)
resourcedata.SetMapValueIfNotNil(rule, "operator", sdkRule.Operator)
resourcedata.SetMapValueIfNotNil(rule, "condition_value", sdkRule.ConditionValue)
resourcedata.SetMapValueIfNotNil(rule, "metric", sdkRule.Metric)
if sdkRule.Groups != nil {
memberGroups := make([]interface{}, 0)
for _, group := range *sdkRule.Groups {
memberGroupMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_id", group.Id)
resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_type", group.VarType)
memberGroups = append(memberGroups, memberGroupMap)
}
rule["groups"] = memberGroups
}
rules = append(rules, rule)
}
return rules
}
package routing_queue_conditional_group_routing
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_routing_queue_conditional_group_routing"
// SetRegistrar registers all the resources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceRoutingQueueConditionalGroupRouting())
regInstance.RegisterExporter(resourceName, RoutingQueueConditionalGroupRoutingExporter())
}
var (
memberGroupResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"member_group_id": {
Description: "ID (GUID) for Group, SkillGroup, Team",
Type: schema.TypeString,
Required: true,
},
"member_group_type": {
Description: "The type of the member group. Accepted values: TEAM, GROUP, SKILLGROUP",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"TEAM", "GROUP", "SKILLGROUP"}, false),
},
},
}
)
// ResourceRoutingQueueConditionalGroupRouting registers the genesyscloud_routing_queue_conditional_group_routing resource with Terraform
func ResourceRoutingQueueConditionalGroupRouting() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud routing queue conditional group routing rules",
CreateContext: provider.CreateWithPooledClient(createRoutingQueueConditionalRoutingGroup),
ReadContext: provider.ReadWithPooledClient(readRoutingQueueConditionalRoutingGroup),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingQueueConditionalRoutingGroup),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingQueueConditionalRoutingGroup),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"queue_id": {
Description: "Id of the routing queue to which the rules belong",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"rules": {
Description: "The Conditional Group Routing settings for the queue.",
Type: schema.TypeList,
Required: true,
MinItems: 1,
MaxItems: 5,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"evaluated_queue_id": {
Description: "The queue being evaluated for this rule. For rule 1, this is always the current queue, so should not be specified.",
Type: schema.TypeString,
Optional: true,
},
"operator": {
Description: "The operator that compares the actual value against the condition value. Valid values: GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"GreaterThan", "LessThan", "GreaterThanOrEqualTo", "LessThanOrEqualTo"}, false),
},
"metric": {
Description: "The queue metric being evaluated. Valid values: EstimatedWaitTime, ServiceLevel.",
Type: schema.TypeString,
Optional: true,
Default: "EstimatedWaitTime",
ValidateFunc: validation.StringInSlice([]string{"EstimatedWaitTime", "ServiceLevel"}, false),
},
"condition_value": {
Description: "The limit value, beyond which a rule evaluates as true.",
Type: schema.TypeFloat,
Required: true,
ValidateFunc: validation.FloatBetween(0, 259200),
},
"wait_seconds": {
Description: "The number of seconds to wait in this rule, if it evaluates as true, before evaluating the next rule. For the final rule, this is ignored, so need not be specified.",
Type: schema.TypeInt,
Optional: true,
Default: 2,
ValidateFunc: validation.IntBetween(0, 259200),
},
"groups": {
Type: schema.TypeList,
Required: true,
MinItems: 1,
Description: "The group(s) to activate if the rule evaluates as true.",
Elem: memberGroupResource,
},
},
},
},
},
}
}
func RoutingQueueConditionalGroupRoutingExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthRoutingQueueConditionalGroup),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"queue_id": {RefType: "genesyscloud_routing_queue"},
"rules.evaluated_queue_id": {RefType: "genesyscloud_routing_queue"},
},
CustomAttributeResolver: map[string]*resourceExporter.RefAttrCustomResolver{
"rules.groups.member_group_id": {ResolverFunc: resourceExporter.MemberGroupsResolver},
"rules.condition_value": {ResolverFunc: resourceExporter.ConditionValueResolver},
},
}
}
package routing_queue_outbound_email_address
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue"
)
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *routingQueueOutboundEmailAddressProxy
type getRoutingQueueOutboundEmailAddressFunc func(ctx context.Context, p *routingQueueOutboundEmailAddressProxy, queueId string) (*platformclientv2.Queueemailaddress, *platformclientv2.APIResponse, error)
type updateRoutingQueueOutboundEmailAddressFunc func(ctx context.Context, p *routingQueueOutboundEmailAddressProxy, queueId string, address *platformclientv2.Queueemailaddress) (*platformclientv2.Queueemailaddress, *platformclientv2.APIResponse, error)
// routingQueueOutboundEmailAddressProxy contains all of the methods that call genesys cloud APIs.
type routingQueueOutboundEmailAddressProxy struct {
clientConfig *platformclientv2.Configuration
routingApi *platformclientv2.RoutingApi
getRoutingQueueOutboundEmailAddressAttr getRoutingQueueOutboundEmailAddressFunc
updateRoutingQueueOutboundEmailAddressAttr updateRoutingQueueOutboundEmailAddressFunc
routingQueueProxy *routingQueue.RoutingQueueProxy
}
// newRoutingQueueOutboundEmailAddressProxy initializes the Routing queue outbound email address proxy with the data needed to communicate with Genesys Cloud
func newRoutingQueueOutboundEmailAddressProxy(clientConfig *platformclientv2.Configuration) *routingQueueOutboundEmailAddressProxy {
api := platformclientv2.NewRoutingApiWithConfig(clientConfig)
routingQueueProxy := routingQueue.GetRoutingQueueProxy(clientConfig)
return &routingQueueOutboundEmailAddressProxy{
clientConfig: clientConfig,
routingApi: api,
getRoutingQueueOutboundEmailAddressAttr: getRoutingQueueOutboundEmailAddressFn,
updateRoutingQueueOutboundEmailAddressAttr: updateRoutingQueueOutboundEmailAddressFn,
routingQueueProxy: routingQueueProxy,
}
}
func getRoutingQueueOutboundEmailAddressProxy(clientConfig *platformclientv2.Configuration) *routingQueueOutboundEmailAddressProxy {
if internalProxy == nil {
internalProxy = newRoutingQueueOutboundEmailAddressProxy(clientConfig)
}
return internalProxy
}
// getRoutingQueueOutboundEmailAddress gets the Outbound Email Address for a queue
func (p *routingQueueOutboundEmailAddressProxy) getRoutingQueueOutboundEmailAddress(ctx context.Context, queueId string) (*platformclientv2.Queueemailaddress, *platformclientv2.APIResponse, error) {
return p.getRoutingQueueOutboundEmailAddressAttr(ctx, p, queueId)
}
// updateRoutingQueueOutboundEmailAddress updates the Outbound Email Address for a queue
func (p *routingQueueOutboundEmailAddressProxy) updateRoutingQueueOutboundEmailAddress(ctx context.Context, queueId string, rules *platformclientv2.Queueemailaddress) (*platformclientv2.Queueemailaddress, *platformclientv2.APIResponse, error) {
return p.updateRoutingQueueOutboundEmailAddressAttr(ctx, p, queueId, rules)
}
// getRoutingQueueOutboundEmailAddressFn is an implementation function for getting the outbound email address for a queue
func getRoutingQueueOutboundEmailAddressFn(ctx context.Context, p *routingQueueOutboundEmailAddressProxy, queueId string) (*platformclientv2.Queueemailaddress, *platformclientv2.APIResponse, error) {
var (
queue *platformclientv2.Queue
resp *platformclientv2.APIResponse
err error
)
queue = rc.GetCacheItem(p.routingQueueProxy.RoutingQueueCache, queueId)
if queue == nil {
queue, resp, err = p.routingApi.GetRoutingQueue(queueId)
if err != nil {
return nil, resp, fmt.Errorf("error when reading queue %s: %s", queueId, err)
}
}
queue, resp, err = p.routingApi.GetRoutingQueue(queueId)
if err != nil {
return nil, resp, fmt.Errorf("error when reading queue %s: %s", queueId, err)
}
// For some reason outbound email address is a double pointer
if queue.OutboundEmailAddress != nil && *queue.OutboundEmailAddress != nil {
return *queue.OutboundEmailAddress, resp, nil
}
return nil, resp, fmt.Errorf("no outbound email address for queue %s", queueId)
}
// updateRoutingQueueOutboundEmailAddressFn is an implementation function for updating the outbound email address for a queue
func updateRoutingQueueOutboundEmailAddressFn(ctx context.Context, p *routingQueueOutboundEmailAddressProxy, queueId string, address *platformclientv2.Queueemailaddress) (*platformclientv2.Queueemailaddress, *platformclientv2.APIResponse, error) {
// Get the routing queue the rules belong to
queue, resp, err := p.routingApi.GetRoutingQueue(queueId)
if err != nil {
return nil, resp, fmt.Errorf("error when reading queue %s: %s", queueId, err)
}
// Copy over all the values from the original object to the new object
updateQueue := platformclientv2.Queuerequest{
Name: queue.Name,
Description: queue.Description,
MemberCount: queue.MemberCount,
UserMemberCount: queue.UserMemberCount,
JoinedMemberCount: queue.JoinedMemberCount,
MediaSettings: queue.MediaSettings,
RoutingRules: queue.RoutingRules,
ConditionalGroupRouting: queue.ConditionalGroupRouting,
Bullseye: queue.Bullseye,
ScoringMethod: queue.ScoringMethod,
AcwSettings: queue.AcwSettings,
SkillEvaluationMethod: queue.SkillEvaluationMethod,
MemberGroups: queue.MemberGroups,
QueueFlow: queue.QueueFlow,
EmailInQueueFlow: queue.EmailInQueueFlow,
MessageInQueueFlow: queue.MessageInQueueFlow,
WhisperPrompt: queue.WhisperPrompt,
OnHoldPrompt: queue.OnHoldPrompt,
AutoAnswerOnly: queue.AutoAnswerOnly,
EnableTranscription: queue.EnableTranscription,
EnableAudioMonitoring: queue.EnableAudioMonitoring,
EnableManualAssignment: queue.EnableManualAssignment,
AgentOwnedRouting: queue.AgentOwnedRouting,
DirectRouting: queue.DirectRouting,
CallingPartyName: queue.CallingPartyName,
CallingPartyNumber: queue.CallingPartyNumber,
DefaultScripts: queue.DefaultScripts,
OutboundMessagingAddresses: queue.OutboundMessagingAddresses,
OutboundEmailAddress: address, // Add the new address
PeerId: queue.PeerId,
SuppressInQueueCallRecording: queue.SuppressInQueueCallRecording,
}
// Update the queue
queue, resp, err = p.routingApi.PutRoutingQueue(queueId, updateQueue)
if err != nil {
return nil, resp, fmt.Errorf("failed to update outbound email address for routing queue %s: %s", queueId, err)
}
if queue.OutboundEmailAddress != nil && *queue.OutboundEmailAddress != nil {
return *queue.OutboundEmailAddress, resp, nil
}
return nil, resp, fmt.Errorf("error updating outbound email address for routing queue %s: %s", queueId, err)
}
package routing_queue_outbound_email_address
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"log"
"strings"
consistencyChecker "terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
featureToggles "terraform-provider-genesyscloud/genesyscloud/util/feature_toggles"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
)
/*
The resource_genesyscloud_routing_queue_outbound-email_address.go contains all the methods that perform the core logic for the resource.
*/
func getAllAuthRoutingQueueOutboundEmailAddress(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
if exists := featureToggles.OEAToggleExists(); !exists {
log.Printf("Environment variable %s not set, skipping exporter for %s", featureToggles.OEAToggleName(), resourceName)
return nil, nil
}
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getRoutingQueueOutboundEmailAddressProxy(clientConfig)
queues, resp, err := proxy.routingQueueProxy.GetAllRoutingQueues(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, "failed to get outbound email addresses for routing queues", resp)
}
for _, queue := range *queues {
if queue.OutboundEmailAddress != nil && *queue.OutboundEmailAddress != nil {
resources[*queue.Id] = &resourceExporter.ResourceMeta{Name: *queue.Name + "-email-address"}
}
}
return resources, nil
}
func createRoutingQueueOutboundEmailAddress(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if exists := featureToggles.OEAToggleExists(); !exists {
return util.BuildDiagnosticError(resourceName, "Environment variable ENABLE_STANDALONE_EMAIL_ADDRESS not set", fmt.Errorf("environment variable %s not set", featureToggles.OEAToggleName()))
}
queueId := d.Get("queue_id").(string)
log.Printf("creating outbound email address for queue %s", queueId)
d.SetId(queueId)
return updateRoutingQueueOutboundEmailAddress(ctx, d, meta)
}
func readRoutingQueueOutboundEmailAddress(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if exists := featureToggles.OEAToggleExists(); !exists {
return util.BuildDiagnosticError(resourceName, "Environment variable ENABLE_STANDALONE_EMAIL_ADDRESS not set", fmt.Errorf("environment variable %s not set", featureToggles.OEAToggleName()))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingQueueOutboundEmailAddressProxy(sdkConfig)
cc := consistencyChecker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingQueueOutboundEmailAddress(), constants.DefaultConsistencyChecks, resourceName)
queueId := d.Id()
log.Printf("Reading outbound email address for queue %s", queueId)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
queueEmailAddress, resp, getErr := proxy.getRoutingQueueOutboundEmailAddress(ctx, queueId)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(fmt.Errorf("failed to read outbound email address for queue %s: %s", queueId, getErr))
}
return retry.NonRetryableError(fmt.Errorf("failed to read outbound email address for queue %s: %s", queueId, getErr))
}
_ = d.Set("queue_id", queueId)
resourcedata.SetNillableReference(d, "domain_id", queueEmailAddress.Domain)
// The route property is a **Inboundroute hence all the checks
if queueEmailAddress.Route != nil && *queueEmailAddress.Route != nil && (*queueEmailAddress.Route).Id != nil {
_ = d.Set("route_id", *(*queueEmailAddress.Route).Id)
}
log.Printf("Reading outbound email address for queue %s", queueId)
return cc.CheckState(d)
})
}
func updateRoutingQueueOutboundEmailAddress(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if exists := featureToggles.OEAToggleExists(); !exists {
return util.BuildDiagnosticError(resourceName, "Environment variable ENABLE_STANDALONE_EMAIL_ADDRESS not set", fmt.Errorf("environment variable %s not set", featureToggles.OEAToggleName()))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingQueueOutboundEmailAddressProxy(sdkConfig)
queueId := d.Id()
inboundRoute := &platformclientv2.Inboundroute{
Id: platformclientv2.String(d.Get("route_id").(string)),
}
emailAddress := platformclientv2.Queueemailaddress{
Domain: &platformclientv2.Domainentityref{
Id: platformclientv2.String(d.Get("domain_id").(string)),
},
Route: &inboundRoute,
}
log.Printf("updating outbound email address for queue %s", queueId)
_, resp, err := proxy.updateRoutingQueueOutboundEmailAddress(ctx, queueId, &emailAddress)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to update outbound email address for queue %s", queueId), resp)
}
log.Printf("updated outbound email address for queue %s", queueId)
return readRoutingQueueOutboundEmailAddress(ctx, d, meta)
}
func deleteRoutingQueueOutboundEmailAddress(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if exists := featureToggles.OEAToggleExists(); !exists {
return util.BuildDiagnosticError(resourceName, "Environment variable ENABLE_STANDALONE_EMAIL_ADDRESS not set", fmt.Errorf("environment variable %s not set", featureToggles.OEAToggleName()))
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingQueueOutboundEmailAddressProxy(sdkConfig)
queueId := d.Id()
log.Printf("Removing email address from queue %s", queueId)
// check if routing queue still exists before trying to remove outbound email address
_, resp, err := proxy.getRoutingQueueOutboundEmailAddress(ctx, queueId)
if err != nil {
if util.IsStatus404(resp) {
log.Printf("outbound email address's parent queue %s already deleted", queueId)
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to remove outbound email address for queue %s", queueId), resp)
}
// To delete, update the queue with an empty email address
var emptyAddress platformclientv2.Queueemailaddress
_, _, err = proxy.updateRoutingQueueOutboundEmailAddress(ctx, queueId, &emptyAddress)
if err != nil && !strings.Contains(err.Error(), "error updating outbound email address for routing queue") {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to remove outbound email address from queue %s", queueId), resp)
}
// Verify there is no email address
rules, _, _ := proxy.getRoutingQueueOutboundEmailAddress(ctx, queueId)
if rules != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("outbound email address still exist for queue %s", queueId), resp)
}
log.Printf("Removed outbound email address from queue %s", queueId)
return nil
}
package routing_queue_outbound_email_address
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_routing_queue_outbound_email_address"
// SetRegistrar registers all the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceRoutingQueueOutboundEmailAddress())
regInstance.RegisterExporter(resourceName, OutboundRoutingQueueOutboundEmailAddressExporter())
}
func ResourceRoutingQueueOutboundEmailAddress() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Routing Queue Outbound Email Address`,
CreateContext: provider.CreateWithPooledClient(createRoutingQueueOutboundEmailAddress),
ReadContext: provider.ReadWithPooledClient(readRoutingQueueOutboundEmailAddress),
UpdateContext: provider.UpdateWithPooledClient(updateRoutingQueueOutboundEmailAddress),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingQueueOutboundEmailAddress),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"queue_id": {
Description: "The routing queue to which the outbound email address is for.",
Type: schema.TypeString,
Required: true,
},
"domain_id": {
Description: "Unique ID of the email domain. e.g. \"test.example.com\"",
Type: schema.TypeString,
Required: true,
},
"route_id": {
Description: "Unique ID of the email route.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func OutboundRoutingQueueOutboundEmailAddressExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthRoutingQueueOutboundEmailAddress),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"queue_id": {RefType: "genesyscloud_routing_queue"},
"route_id": {RefType: "genesyscloud_routing_email_route"},
"domain_id": {RefType: "genesyscloud_routing_email_domain"},
},
}
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceRoutingSmsAddressRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
smsAddressProxy := getRoutingSmsAddressProxy(sdkConfig)
name := d.Get("name").(string)
log.Printf("Searching for routing sms address with name '%s'", name)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
smsAddressId, retryable, resp, err := smsAddressProxy.getSmsAddressIdByName(name, ctx)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get SMS Address | error: %s", err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get SMS Address | error: %s", err), resp))
}
d.SetId(smsAddressId)
return nil
})
}
package genesyscloud
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// Type definitions for each func on our proxy so we can easily mock them out later
type createSmsAddressFunc func(p *routingSmsAddressProxy, body platformclientv2.Smsaddressprovision) (*platformclientv2.Smsaddress, *platformclientv2.APIResponse, error)
type getAllSmsAddressesFunc func(p *routingSmsAddressProxy, ctx context.Context) (*[]platformclientv2.Smsaddress, *platformclientv2.APIResponse, error)
type getSmsAddressByIdFunc func(p *routingSmsAddressProxy, id string) (*platformclientv2.Smsaddress, *platformclientv2.APIResponse, error)
type getSmsAddressIdByNameFunc func(p *routingSmsAddressProxy, name string, ctx context.Context) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
type deleteSmsAddressByIdFunc func(p *routingSmsAddressProxy, id string) (*platformclientv2.APIResponse, error)
// routingSmsAddressProxy contains all of the methods that call genesys cloud APIs.
type routingSmsAddressProxy struct {
routingApi *platformclientv2.RoutingApi
createSmsAddressAttr createSmsAddressFunc
getAllSmsAddressesAttr getAllSmsAddressesFunc
getSmsAddressByIdAttr getSmsAddressByIdFunc
getSmsAddressIdByNameAttr getSmsAddressIdByNameFunc
deleteSmsAddressByIdAttr deleteSmsAddressByIdFunc
}
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *routingSmsAddressProxy
// newRoutingSmsAddressProxy initializes the sms address proxy with all of the data needed to communicate with Genesys Cloud
func newRoutingSmsAddressProxy(clientConfig *platformclientv2.Configuration) *routingSmsAddressProxy {
api := platformclientv2.NewRoutingApiWithConfig(clientConfig)
return &routingSmsAddressProxy{
routingApi: api,
createSmsAddressAttr: createSmsAddressFn,
getAllSmsAddressesAttr: getAllSmsAddressesFn,
getSmsAddressByIdAttr: getSmsAddressByIdFn,
getSmsAddressIdByNameAttr: getSmsAddressIdByNameFn,
deleteSmsAddressByIdAttr: deleteSmsAddressByIdFn,
}
}
// getRoutingSmsAddressProxy acts as a singleton for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getRoutingSmsAddressProxy(clientConfig *platformclientv2.Configuration) *routingSmsAddressProxy {
if internalProxy == nil {
internalProxy = newRoutingSmsAddressProxy(clientConfig)
}
return internalProxy
}
// createSmsAddress creates a Genesys Cloud Sms Address
func (p *routingSmsAddressProxy) createSmsAddress(body platformclientv2.Smsaddressprovision) (*platformclientv2.Smsaddress, *platformclientv2.APIResponse, error) {
return p.createSmsAddressAttr(p, body)
}
// getSmsAddressById gets a Genesys Cloud Sms Address by ID
func (p *routingSmsAddressProxy) getSmsAddressById(id string) (*platformclientv2.Smsaddress, *platformclientv2.APIResponse, error) {
return p.getSmsAddressByIdAttr(p, id)
}
// getSmsAddressIdByName gets a Genesys Cloud Sms Address ID by name
func (p *routingSmsAddressProxy) getSmsAddressIdByName(name string, ctx context.Context) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getSmsAddressIdByNameAttr(p, name, ctx)
}
// getAllSmsAddresses gets all Genesys Cloud Sms Addresses
func (p *routingSmsAddressProxy) getAllSmsAddresses(ctx context.Context) (*[]platformclientv2.Smsaddress, *platformclientv2.APIResponse, error) {
return p.getAllSmsAddressesAttr(p, ctx)
}
// deleteSmsAddress deletes a Genesys Cloud Sms Address by ID
func (p *routingSmsAddressProxy) deleteSmsAddress(id string) (*platformclientv2.APIResponse, error) {
return p.deleteSmsAddressByIdAttr(p, id)
}
// createSmsAddressFn is an implementation function for creating a Genesys Cloud Sms Address
func createSmsAddressFn(p *routingSmsAddressProxy, body platformclientv2.Smsaddressprovision) (*platformclientv2.Smsaddress, *platformclientv2.APIResponse, error) {
return p.routingApi.PostRoutingSmsAddresses(body)
}
// getAllSmsAddressesFn is an implementation function for getting all Sms Addresses
func getAllSmsAddressesFn(p *routingSmsAddressProxy, ctx context.Context) (*[]platformclientv2.Smsaddress, *platformclientv2.APIResponse, error) {
var allSmsAddresses []platformclientv2.Smsaddress
var response *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
const pageSize = 100
smsAddresses, resp, getErr := p.routingApi.GetRoutingSmsAddresses(pageSize, pageNum)
if getErr != nil {
return nil, resp, fmt.Errorf("error requesting page of Routing Sms Addresses: %s", getErr)
}
response = resp
if smsAddresses.Entities == nil || len(*smsAddresses.Entities) == 0 {
break
}
for _, entity := range *smsAddresses.Entities {
allSmsAddresses = append(allSmsAddresses, entity)
}
}
return &allSmsAddresses, response, nil
}
// getSmsAddressByIdFn is an implementation function for getting a Genesys Cloud Sms Address by ID
func getSmsAddressByIdFn(p *routingSmsAddressProxy, id string) (*platformclientv2.Smsaddress, *platformclientv2.APIResponse, error) {
return p.routingApi.GetRoutingSmsAddress(id)
}
// getSmsAddressIdByNameFn is an implementation function for getting a sms address ID by name.
func getSmsAddressIdByNameFn(p *routingSmsAddressProxy, name string, ctx context.Context) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
smsAddresses, resp, err := getAllSmsAddressesFn(p, ctx)
if err != nil {
return "", false, resp, fmt.Errorf("failed to read sms addresses: %v", err)
}
if smsAddresses == nil || len(*smsAddresses) == 0 {
return "", true, resp, fmt.Errorf("failed to read sms addresses: %v", err)
}
for _, address := range *smsAddresses {
if *address.Name == name {
return *address.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("failed to find sms address with name '%s'", name)
}
// deleteSmsAddressByIdFn is an implementation function for deleting a Genesys Cloud Sms Address by ID
func deleteSmsAddressByIdFn(p *routingSmsAddressProxy, id string) (*platformclientv2.APIResponse, error) {
return p.routingApi.DeleteRoutingSmsAddress(id)
}
package genesyscloud
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
// SetRegistrar registers all the resources, data sources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceRoutingSmsAddress())
l.RegisterResource(resourceName, ResourceRoutingSmsAddress())
l.RegisterExporter(resourceName, RoutingSmsAddressExporter())
}
func RoutingSmsAddressExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingSmsAddress),
}
}
func ResourceRoutingSmsAddress() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud routing sms address`,
CreateContext: provider.CreateWithPooledClient(createRoutingSmsAddress),
ReadContext: provider.ReadWithPooledClient(readRoutingSmsAddress),
DeleteContext: provider.DeleteWithPooledClient(deleteRoutingSmsAddress),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `Name associated with this address`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
},
`street`: {
Description: `The number and street address where this address is located.`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
},
`city`: {
Description: `The city in which this address is in`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
},
`region`: {
Description: `The state or region this address is in`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
},
`postal_code`: {
Description: `The postal code this address is in`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
},
`country_code`: {
Description: `The ISO country code of this address`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
ValidateDiagFunc: gcloud.ValidateCountryCode,
},
`auto_correct_address`: {
Description: `This is used when the address is created. If the value is not set or true, then the system will, if necessary, auto-correct the address you provide. Set this value to false if the system should not auto-correct the address.`,
Optional: true,
ForceNew: true,
Type: schema.TypeBool,
},
},
}
}
func DataSourceRoutingSmsAddress() *schema.Resource {
return &schema.Resource{
Description: `Data source for Genesys Cloud Routing Sms Address. Select a Routing Sms Address by name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceRoutingSmsAddressRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: `Routing Sms Address name.`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package genesyscloud
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
const resourceName = "genesyscloud_routing_sms_address"
func getAllRoutingSmsAddress(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getRoutingSmsAddressProxy(clientConfig)
allSmsAddresses, resp, err := proxy.getAllSmsAddresses(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get sms addresses error: %s", err), resp)
}
for _, entity := range *allSmsAddresses {
var name string
if entity.Name != nil {
name = *entity.Name
} else {
name = *entity.Id
}
resources[*entity.Id] = &resourceExporter.ResourceMeta{Name: name}
}
return resources, nil
}
func createRoutingSmsAddress(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
street := d.Get("street").(string)
city := d.Get("city").(string)
region := d.Get("region").(string)
postalCode := d.Get("postal_code").(string)
countryCode := d.Get("country_code").(string)
autoCorrectAddress := d.Get("auto_correct_address").(bool)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingSmsAddressProxy(sdkConfig)
sdkSmsAddressProvision := platformclientv2.Smsaddressprovision{
AutoCorrectAddress: &autoCorrectAddress,
}
if name != "" {
sdkSmsAddressProvision.Name = &name
}
if street != "" {
sdkSmsAddressProvision.Street = &street
}
if city != "" {
sdkSmsAddressProvision.City = &city
}
if region != "" {
sdkSmsAddressProvision.Region = ®ion
}
if postalCode != "" {
sdkSmsAddressProvision.PostalCode = &postalCode
}
if countryCode != "" {
sdkSmsAddressProvision.CountryCode = &countryCode
}
log.Printf("Creating Routing Sms Address %s", name)
routingSmsAddress, resp, err := proxy.createSmsAddress(sdkSmsAddressProvision)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create sms address %s error: %s", name, err), resp)
}
d.SetId(*routingSmsAddress.Id)
log.Printf("Created Routing Sms Address %s %s", name, *routingSmsAddress.Id)
return readRoutingSmsAddress(ctx, d, meta)
}
func readRoutingSmsAddress(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingSmsAddressProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingSmsAddress(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Routing Sms Address %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
sdkSmsAddress, resp, getErr := proxy.getSmsAddressById(d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Routing Sms Address %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Routing Sms Address %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", sdkSmsAddress.Name)
resourcedata.SetNillableValue(d, "street", sdkSmsAddress.Street)
resourcedata.SetNillableValue(d, "city", sdkSmsAddress.City)
resourcedata.SetNillableValue(d, "region", sdkSmsAddress.Region)
resourcedata.SetNillableValue(d, "postal_code", sdkSmsAddress.PostalCode)
resourcedata.SetNillableValue(d, "country_code", sdkSmsAddress.CountryCode)
log.Printf("Read Routing Sms Address %s", d.Id())
return cc.CheckState(d)
})
}
func deleteRoutingSmsAddress(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingSmsAddressProxy(sdkConfig)
// AD-123 is the ID for a default address returned to all test orgs, it can't be deleted
if d.Id() == "AD-123" {
return nil
}
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting Routing Sms Address")
resp, err := proxy.deleteSmsAddress(d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete routing sms address %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getSmsAddressById(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Routing Sms Address deleted
log.Printf("Deleted Routing Sms Address %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Routing Sms Address %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Routing Sms Address %s still exists", d.Id()), resp))
})
}
package scripts
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
DataSource for the Scripts resource
*/
// dataSourceScriptRead provides the main terraform code needed to read a script resource by name
func dataSourceScriptRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
scriptsProxy := getScriptsProxy(sdkConfig)
name := d.Get("name").(string)
// Query for scripts by name. Retry in case new script is not yet indexed by search.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
scriptId, retryable, resp, err := scriptsProxy.getScriptIdByName(ctx, name)
if err != nil {
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get Script %s", err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get Script %s", err), resp))
}
d.SetId(scriptId)
return nil
})
}
package scripts
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
rc "terraform-provider-genesyscloud/genesyscloud/resource_cache"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/files"
"time"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_scripts_proxy.go file contains all of the logic associated with calling the Genesys cloud API for scripts.
*/
var internalProxy *scriptsProxy
type createScriptFunc func(ctx context.Context, filePath, scriptName string, substitutions map[string]interface{}, p *scriptsProxy) (scriptId string, err error)
type updateScriptFunc func(ctx context.Context, filePath, scriptName, scriptId string, substitutions map[string]interface{}, p *scriptsProxy) (id string, err error)
type getAllPublishedScriptsFunc func(ctx context.Context, p *scriptsProxy) (*[]platformclientv2.Script, *platformclientv2.APIResponse, error)
type publishScriptFunc func(ctx context.Context, p *scriptsProxy, scriptId string) (*platformclientv2.APIResponse, error)
type getScriptsByNameFunc func(ctx context.Context, p *scriptsProxy, scriptName string) ([]platformclientv2.Script, *platformclientv2.APIResponse, error)
type getScriptIdByNameFunc func(ctx context.Context, p *scriptsProxy, name string) (scriptId string, retryable bool, resp *platformclientv2.APIResponse, err error)
type verifyScriptUploadSuccessFunc func(ctx context.Context, p *scriptsProxy, body []byte) (bool, error)
type scriptWasUploadedSuccessfullyFunc func(ctx context.Context, p *scriptsProxy, uploadId string) (bool, *platformclientv2.APIResponse, error)
type getScriptExportUrlFunc func(ctx context.Context, p *scriptsProxy, scriptId string) (string, *platformclientv2.APIResponse, error)
type deleteScriptFunc func(ctx context.Context, p *scriptsProxy, scriptId string) error
type getScriptByIdFunc func(ctx context.Context, p *scriptsProxy, scriptId string) (script *platformclientv2.Script, resp *platformclientv2.APIResponse, err error)
type getPublishedScriptsByNameFunc func(ctx context.Context, p *scriptsProxy, name string) (*[]platformclientv2.Script, *platformclientv2.APIResponse, error)
// scriptsProxy contains all of the method used to interact with the Genesys Scripts SDK
type scriptsProxy struct {
clientConfig *platformclientv2.Configuration
scriptsApi *platformclientv2.ScriptsApi
basePath string
accessToken string
createScriptAttr createScriptFunc
updateScriptAttr updateScriptFunc
getAllScriptsAttr getAllPublishedScriptsFunc
publishScriptAttr publishScriptFunc
getScriptIdByNameAttr getScriptIdByNameFunc
getScriptsByNameAttr getScriptsByNameFunc
verifyScriptUploadSuccessAttr verifyScriptUploadSuccessFunc
scriptWasUploadedSuccessfullyAttr scriptWasUploadedSuccessfullyFunc
getScriptExportUrlAttr getScriptExportUrlFunc
deleteScriptAttr deleteScriptFunc
getScriptByIdAttr getScriptByIdFunc
getPublishedScriptsByNameAttr getPublishedScriptsByNameFunc
scriptCache rc.CacheInterface[platformclientv2.Script]
}
// getScriptsProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getScriptsProxy(clientConfig *platformclientv2.Configuration) *scriptsProxy {
if internalProxy == nil {
internalProxy = newScriptsProxy(clientConfig)
}
return internalProxy
}
// newScriptsProxy initializes the Scripts proxy with all of the data needed to communicate with Genesys Cloud
func newScriptsProxy(clientConfig *platformclientv2.Configuration) *scriptsProxy {
scriptsAPI := platformclientv2.NewScriptsApiWithConfig(clientConfig)
scriptCache := rc.NewResourceCache[platformclientv2.Script]()
return &scriptsProxy{
clientConfig: clientConfig,
scriptsApi: scriptsAPI,
basePath: strings.Replace(scriptsAPI.Configuration.BasePath, "api", "apps", -1),
accessToken: scriptsAPI.Configuration.AccessToken,
createScriptAttr: createScriptFn,
updateScriptAttr: updateScriptFn,
getAllScriptsAttr: getAllPublishedScriptsFn,
publishScriptAttr: publishScriptFn,
getScriptIdByNameAttr: getScriptIdByNameFn,
getScriptsByNameAttr: getScriptsByNameFn,
verifyScriptUploadSuccessAttr: verifyScriptUploadSuccessFn,
scriptWasUploadedSuccessfullyAttr: scriptWasUploadedSuccessfullyFn,
getScriptExportUrlAttr: getScriptExportUrlFn,
deleteScriptAttr: deleteScriptFn,
getScriptByIdAttr: getScriptByIdFn,
getPublishedScriptsByNameAttr: getPublishedScriptsByNameFn,
scriptCache: scriptCache,
}
}
// createScript creates a Genesys Cloud Script
func (p *scriptsProxy) createScript(ctx context.Context, filePath, scriptName string, substitutions map[string]interface{}) (string, error) {
return p.createScriptAttr(ctx, filePath, scriptName, substitutions, p)
}
// updateScript updates a Genesys Cloud Script
func (p *scriptsProxy) updateScript(ctx context.Context, filePath, scriptName, scriptId string, substitutions map[string]interface{}) (string, error) {
return p.updateScriptAttr(ctx, filePath, scriptName, scriptId, substitutions, p)
}
func (p *scriptsProxy) getAllPublishedScripts(ctx context.Context) (*[]platformclientv2.Script, *platformclientv2.APIResponse, error) {
return p.getAllScriptsAttr(ctx, p)
}
func (p *scriptsProxy) publishScript(ctx context.Context, scriptId string) (*platformclientv2.APIResponse, error) {
return p.publishScriptAttr(ctx, p, scriptId)
}
func (p *scriptsProxy) getScriptsByName(ctx context.Context, scriptName string) ([]platformclientv2.Script, *platformclientv2.APIResponse, error) {
return p.getScriptsByNameAttr(ctx, p, scriptName)
}
func (p *scriptsProxy) getScriptIdByName(ctx context.Context, name string) (string, bool, *platformclientv2.APIResponse, error) {
return p.getScriptIdByNameAttr(ctx, p, name)
}
func (p *scriptsProxy) verifyScriptUploadSuccess(ctx context.Context, body []byte) (bool, error) {
return p.verifyScriptUploadSuccessAttr(ctx, p, body)
}
func (p *scriptsProxy) scriptWasUploadedSuccessfully(ctx context.Context, uploadId string) (bool, *platformclientv2.APIResponse, error) {
return p.scriptWasUploadedSuccessfullyAttr(ctx, p, uploadId)
}
func (p *scriptsProxy) getScriptExportUrl(ctx context.Context, scriptId string) (string, *platformclientv2.APIResponse, error) {
return p.getScriptExportUrlAttr(ctx, p, scriptId)
}
func (p *scriptsProxy) deleteScript(ctx context.Context, scriptId string) error {
return p.deleteScriptAttr(ctx, p, scriptId)
}
func (p *scriptsProxy) getScriptById(ctx context.Context, scriptId string) (script *platformclientv2.Script, resp *platformclientv2.APIResponse, err error) {
return p.getScriptByIdAttr(ctx, p, scriptId)
}
func (p *scriptsProxy) getPublishedScriptsByName(ctx context.Context, name string) (*[]platformclientv2.Script, *platformclientv2.APIResponse, error) {
return p.getPublishedScriptsByNameAttr(ctx, p, name)
}
// publishScriptFn will publish the script after it has been successfully upload
func publishScriptFn(_ context.Context, p *scriptsProxy, scriptId string) (*platformclientv2.APIResponse, error) {
publishScriptBody := &platformclientv2.Publishscriptrequestdata{
ScriptId: &scriptId,
}
_, resp, err := p.scriptsApi.PostScriptsPublished("0", *publishScriptBody)
return resp, err
}
// getAllPublishedScriptsFn returns all published scripts within a Genesys Cloud instance
func getAllPublishedScriptsFn(_ context.Context, p *scriptsProxy) (*[]platformclientv2.Script, *platformclientv2.APIResponse, error) {
var allScripts []platformclientv2.Script
var response *platformclientv2.APIResponse
pageSize := 50
for pageNum := 1; ; pageNum++ {
scripts, resp, err := p.scriptsApi.GetScripts(pageSize, pageNum, "", "", "", "", "", "", "", "")
response = resp
if err != nil {
return nil, resp, err
}
if scripts.Entities == nil || len(*scripts.Entities) == 0 {
break
}
for _, script := range *scripts.Entities {
_, resp, err := p.scriptsApi.GetScriptsPublishedScriptId(*script.Id, "")
response = resp
//If the item is not found this indicates it is not published
if resp.StatusCode == http.StatusNotFound && err == nil {
log.Printf("Script id %s, script %s name is not published and will not be returned for export", *script.Id, *script.Name)
continue
}
//Some APIs will return an error code even if the response code is a 404.
if resp.StatusCode == http.StatusNotFound && err != nil {
log.Printf("Script id %s, script %s name is not published and will not be returned for export. Also an err was returned on call %s", *script.Id, *script.Name, err)
continue
}
//All other errors should be failed
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve publication status for script id %s. Err: %v", *script.Id, err)
}
allScripts = append(allScripts, script)
}
}
for _, script := range allScripts {
rc.SetCache(p.scriptCache, *script.Id, script)
}
return &allScripts, response, nil
}
// getScriptsByNameFn Retrieves all scripts instances that match the name passed in
func getScriptsByNameFn(_ context.Context, p *scriptsProxy, scriptName string) ([]platformclientv2.Script, *platformclientv2.APIResponse, error) {
const pageSize = 50
var (
scripts []platformclientv2.Script
response *platformclientv2.APIResponse
processedScriptIds []string
)
log.Printf("Retrieving scripts with name '%s'", scriptName)
for pageNum := 1; ; pageNum++ {
data, response, err := p.scriptsApi.GetScripts(pageSize, pageNum, "", scriptName, "", "", "", "", "", "")
if err != nil {
return scripts, response, err
}
if data.Entities == nil || len(*data.Entities) == 0 {
break
}
for _, script := range *data.Entities {
if *script.Name == scriptName {
scripts = append(scripts, script)
processedScriptIds = append(processedScriptIds, *script.Id)
}
}
}
for pageNum := 1; ; pageNum++ {
data, response, err := p.scriptsApi.GetScriptsPublished(pageSize, pageNum, "", scriptName, "", "", "", "")
if err != nil {
return nil, response, err
}
if data.Entities == nil || len(*data.Entities) == 0 {
break
}
for _, script := range *data.Entities {
if *script.Name == scriptName && !util.StringExists(*script.Id, processedScriptIds) {
scripts = append(scripts, script)
processedScriptIds = append(processedScriptIds, *script.Id)
}
}
}
return scripts, response, nil
}
// createScriptFormData creates the form data attributes to create a script in Genesys Cloud
func (p *scriptsProxy) createScriptFormData(filePath, scriptName, scriptId string) (map[string]io.Reader, error) {
fileReader, _, err := files.DownloadOrOpenFile(filePath)
if err != nil {
return nil, err
}
formData := make(map[string]io.Reader, 0)
formData["file"] = fileReader
formData["scriptName"] = strings.NewReader(scriptName)
if scriptId != "" {
formData["scriptIdToReplace"] = strings.NewReader(scriptId)
}
return formData, nil
}
// uploadScriptFile uploads a script file to S3
// For creates, scriptId should be an empty string
func (p *scriptsProxy) uploadScriptFile(filePath, scriptName, scriptId string, substitutions map[string]interface{}) ([]byte, error) {
formData, err := p.createScriptFormData(filePath, scriptName, scriptId)
if err != nil {
return nil, err
}
headers := make(map[string]string)
headers["Authorization"] = "Bearer " + p.accessToken
s3Uploader := files.NewS3Uploader(nil, formData, substitutions, headers, "POST", p.basePath+"/uploads/v2/scripter")
resp, err := s3Uploader.Upload()
return resp, err
}
// getScriptIdByNameFn is the implementation function for retrieving a script ID by name, if no other scripts have the same name
func getScriptIdByNameFn(ctx context.Context, p *scriptsProxy, name string) (string, bool, *platformclientv2.APIResponse, error) {
sdkScripts, resp, err := p.getScriptsByName(ctx, name)
if err != nil {
return "", false, resp, err
}
if len(sdkScripts) > 1 {
var extraErrorInfo string
if isNameOfDefaultScript(name) {
extraErrorInfo = fmt.Sprintf("'%s' is the name of a reserved script in Genesys Cloud that cannot be deleted. Please select another name.", name)
}
return "", false, resp, fmt.Errorf("more than one script found with name '%s'. %s", name, extraErrorInfo)
}
if len(sdkScripts) == 0 {
return "", true, resp, fmt.Errorf("no script found with name '%s'", name)
}
return *sdkScripts[0].Id, false, resp, nil
}
func isNameOfDefaultScript(name string) bool {
return name == constants.DefaultOutboundScriptName || name == constants.DefaultInboundScriptName || name == constants.DefaultCallbackScriptName
}
// verifyScriptUploadSuccessFn checks to see if a file has successfully uploaded
func verifyScriptUploadSuccessFn(ctx context.Context, p *scriptsProxy, body []byte) (bool, error) {
uploadId, err := p.getUploadIdFromBody(body)
if err != nil {
return false, err
}
maxRetries := 3
for i := 1; i <= maxRetries; i++ {
time.Sleep(2 * time.Second)
isUploadSuccess, _, err := p.scriptWasUploadedSuccessfully(ctx, uploadId)
if err != nil {
return false, err
}
if isUploadSuccess {
return true, nil
}
}
return false, nil
}
// getUploadIdFromBody retrieves the upload Id from the json file being uploade
func (p *scriptsProxy) getUploadIdFromBody(body []byte) (string, error) {
var (
jsonData interface{}
uploadId string
)
if err := json.Unmarshal(body, &jsonData); err != nil {
return "", fmt.Errorf("error unmarshalling json: %v", err)
}
if jsonMap, ok := jsonData.(map[string]interface{}); ok {
uploadId = jsonMap["correlationId"].(string)
}
return uploadId, nil
}
// scriptWasUploadedSuccessfullyFn checks the Genesys Cloud API to see if the script was successfully uploaded
func scriptWasUploadedSuccessfullyFn(_ context.Context, p *scriptsProxy, uploadId string) (bool, *platformclientv2.APIResponse, error) {
data, resp, err := p.scriptsApi.GetScriptsUploadStatus(uploadId, false)
if err != nil {
return false, resp, err
}
if resp.StatusCode != http.StatusOK {
return false, resp, fmt.Errorf("error calling GetScriptsUploadStatus: %v", resp.Status)
}
return *data.Succeeded, resp, nil
}
// getScriptExportUrlFn retrieves the export URL for a targeted script
func getScriptExportUrlFn(_ context.Context, p *scriptsProxy, scriptId string) (string, *platformclientv2.APIResponse, error) {
var (
body platformclientv2.Exportscriptrequest
)
data, resp, err := p.scriptsApi.PostScriptExport(scriptId, body)
if err != nil {
return "", resp, fmt.Errorf("error calling PostScriptExport: %v", err)
}
if resp.StatusCode != http.StatusOK {
return "", resp, fmt.Errorf("error calling PostScriptExport: %v", resp.Status)
}
return *data.Url, resp, nil
}
// deleteScriptFn deletes a script from Genesys Cloud
func deleteScriptFn(_ context.Context, p *scriptsProxy, scriptId string) error {
fullPath := p.scriptsApi.Configuration.BasePath + "/api/v2/scripts/" + scriptId
r, _ := http.NewRequest(http.MethodDelete, fullPath, nil)
r.Header.Set("Authorization", "Bearer "+p.scriptsApi.Configuration.AccessToken)
r.Header.Set("Content-Type", "application/json")
log.Printf("Deleting script %s", scriptId)
client := &http.Client{}
resp, err := client.Do(r)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to delete script %s: %s", scriptId, resp.Status)
}
log.Printf("Successfully deleted script %s", scriptId)
return nil
}
// getScriptByIdFn retrieves a script by Id
func getScriptByIdFn(_ context.Context, p *scriptsProxy, scriptId string) (script *platformclientv2.Script, resp *platformclientv2.APIResponse, err error) {
if script := rc.GetCacheItem(p.scriptCache, scriptId); script != nil {
return script, nil, nil
}
script, resp, err = p.scriptsApi.GetScript(scriptId)
if err != nil {
if resp.StatusCode == http.StatusNotFound {
return nil, resp, nil
}
return nil, resp, err
}
return script, resp, nil
}
// getPublishedScriptsByNameFn returns all of the published scripts that match a name. Note: Genesys Cloud allows two script to have the same name and published so we have to return all of the published scripts and let the consumer sort it out.
func getPublishedScriptsByNameFn(_ context.Context, p *scriptsProxy, name string) (*[]platformclientv2.Script, *platformclientv2.APIResponse, error) {
const pageSize = 100
var allPublishedScripts []platformclientv2.Script
var response *platformclientv2.APIResponse
for i := 0; ; i++ {
pageNumber := i + 1
data, resp, err := p.scriptsApi.GetScriptsPublished(pageSize, pageNumber, "", name, "", "", "", "")
if err != nil {
return nil, resp, err
}
response = resp
if data.Entities == nil || len(*data.Entities) == 0 {
break
}
for _, script := range *data.Entities {
if *script.Name == name {
allPublishedScripts = append(allPublishedScripts, script)
}
}
}
return &allPublishedScripts, response, nil
}
// createScriptFn is an implementation function for creating a Genesys Cloud Script
func createScriptFn(ctx context.Context, filePath, scriptName string, substitutions map[string]interface{}, p *scriptsProxy) (string, error) {
exists, err := scriptExistsWithName(ctx, p, scriptName)
if err != nil {
return "", err
}
if exists {
return "", fmt.Errorf("script with name '%s' already exists. Please provide a unique name", scriptName)
}
resp, err := p.uploadScriptFile(filePath, scriptName, "", substitutions)
if err != nil {
return "", err
}
success, err := p.verifyScriptUploadSuccess(ctx, resp)
if err != nil {
return "", err
} else if !success {
return "", fmt.Errorf("script '%s' failed to upload successfully", scriptName)
}
scriptId, _, _, err := p.getScriptIdByName(ctx, scriptName)
if err != nil {
return "", err
}
if resp, err := p.publishScript(ctx, scriptId); err != nil {
return "", fmt.Errorf("script '%s' with id '%s' was not successfully published: %v %v", scriptName, scriptId, err, resp)
}
return scriptId, nil
}
// updateScriptFn is an implementation function for updating a Genesys Cloud Script
func updateScriptFn(ctx context.Context, filePath, scriptName, scriptId string, substitutions map[string]interface{}, p *scriptsProxy) (string, error) {
resp, err := p.uploadScriptFile(filePath, scriptName, scriptId, substitutions)
if err != nil {
return "", err
}
success, err := p.verifyScriptUploadSuccess(ctx, resp)
if err != nil {
return "", err
} else if !success {
return "", fmt.Errorf("script '%s' failed to upload successfully", scriptName)
}
scriptIdAfterUpdate, _, _, err := p.getScriptIdByName(ctx, scriptName)
if err != nil {
return "", err
}
if resp, err := p.publishScript(ctx, scriptIdAfterUpdate); err != nil {
return "", fmt.Errorf("script '%s' with id '%s' was not successfully published: %v %v", scriptName, scriptIdAfterUpdate, err, resp)
}
return scriptIdAfterUpdate, nil
}
// scriptExistsWithName is a helper method to determine if a script already exists with the name the user is trying to create a script with
func scriptExistsWithName(ctx context.Context, scriptsProxy *scriptsProxy, scriptName string) (bool, error) {
sdkScripts, _, err := scriptsProxy.getScriptsByName(ctx, scriptName)
if err != nil {
return true, err
}
if len(sdkScripts) < 1 {
return false, nil
}
return true, nil
}
package scripts
import (
"context"
"fmt"
"log"
"net/http"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// getAllScripts returns all the published scripts
func getAllScripts(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
scriptsProxy := getScriptsProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
scripts, resp, err := scriptsProxy.getAllPublishedScripts(ctx)
if err != nil {
return resources, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of scripts error: %s", err), resp)
}
for _, script := range *scripts {
resources[*script.Id] = &resourceExporter.ResourceMeta{Name: *script.Name}
}
return resources, nil
}
// createScript providers the Terraform resource logic for creating a Script object
func createScript(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
scriptsProxy := getScriptsProxy(sdkConfig)
filePath := d.Get("filepath").(string)
scriptName := d.Get("script_name").(string)
substitutions := d.Get("substitutions").(map[string]interface{})
log.Printf("Creating script %s", scriptName)
scriptId, err := scriptsProxy.createScript(ctx, filePath, scriptName, substitutions)
if err != nil {
return diag.FromErr(err)
}
d.SetId(scriptId)
log.Printf("Created script %s. ", d.Id())
return readScript(ctx, d, meta)
}
func updateScript(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
scriptsProxy := getScriptsProxy(sdkConfig)
filePath := d.Get("filepath").(string)
scriptName := d.Get("script_name").(string)
substitutions := d.Get("substitutions").(map[string]interface{})
log.Printf("Updating script '%s' %s", scriptName, d.Id())
scriptId, err := scriptsProxy.updateScript(ctx, filePath, scriptName, d.Id(), substitutions)
if err != nil {
return diag.FromErr(err)
}
if scriptId != d.Id() {
log.Printf("ID of script '%s' changed from '%s' to '%s' after update.", scriptName, d.Id(), scriptId)
d.SetId(scriptId)
}
log.Printf("Updated script '%s' %s", scriptName, d.Id())
return readScript(ctx, d, meta)
}
// readScript contains all of the logic needed to read resource data from Genesys Cloud
func readScript(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
scriptsProxy := getScriptsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceScript(), constants.DefaultConsistencyChecks, resourceName)
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
script, resp, err := scriptsProxy.getScriptById(ctx, d.Id())
if resp != nil && resp.StatusCode == http.StatusNotFound {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow %s | error: %s", d.Id(), err), resp))
}
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read flow %s | error: %s", d.Id(), err), resp))
}
if script.Name != nil {
_ = d.Set("script_name", *script.Name)
}
log.Printf("Read script %s %s", d.Id(), *script.Name)
return cc.CheckState(d)
})
}
// deleteScript contains all the logic needed to delete a resource from Genesys Cloud
func deleteScript(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
scriptsProxy := getScriptsProxy(sdkConfig)
log.Printf("Deleting script %s", d.Id())
if err := scriptsProxy.deleteScript(ctx, d.Id()); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to delete script %s", d.Id()), err)
}
log.Printf("Successfully deleted script %s", d.Id())
return nil
}
package scripts
import (
"context"
"fmt"
"os"
"path"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util/files"
)
// ScriptResolver is used to download all Genesys Cloud scripts from Genesys Cloud
func ScriptResolver(scriptId, exportDirectory, subDirectory string, configMap map[string]interface{}, meta interface{}) error {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
scriptsProxy := getScriptsProxy(sdkConfig)
exportFileName := fmt.Sprintf("script-%s.json", scriptId)
fullPath := path.Join(exportDirectory, subDirectory)
if err := os.MkdirAll(fullPath, os.ModePerm); err != nil {
return err
}
ctx := context.Background()
url, _, err := scriptsProxy.getScriptExportUrl(ctx, scriptId)
if err != nil {
return err
}
if err := files.DownloadExportFile(fullPath, exportFileName, url); err != nil {
return err
}
// Update filepath field in configMap to point to exported script file
configMap["filepath"] = path.Join(subDirectory, exportFileName)
configMap["file_content_hash"] = fmt.Sprintf(`${filesha256("%s")}`, path.Join(subDirectory, exportFileName))
return err
}
package scripts
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
/*
Defines the resource schema, the datasource, and the exporters for the scripts package
*/
const resourceName = "genesyscloud_script"
// SetRegistrar registers all the resources, data sources and exporters in the packages
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceScript())
l.RegisterResource(resourceName, ResourceScript())
l.RegisterExporter(resourceName, ExporterScript())
}
// DataSourceScript returns the data source schema definition
func DataSourceScript() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Scripts. Select a script by name. This will only search on published scripts. Unpublished scripts will not be returned",
ReadContext: provider.ReadWithPooledClient(dataSourceScriptRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Script name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
// ResourceScript returns the resource script definitions
func ResourceScript() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Script",
CreateContext: provider.CreateWithPooledClient(createScript),
ReadContext: provider.ReadWithPooledClient(readScript),
UpdateContext: provider.UpdateWithPooledClient(updateScript),
DeleteContext: provider.DeleteWithPooledClient(deleteScript),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"script_name": {
Description: "Display name for the script. A reliably unique name is recommended. Updating this field will result in the script being dropped and recreated with a new GUID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"filepath": {
Description: "Path to the script file to upload.",
Type: schema.TypeString,
Required: true,
ValidateFunc: gcloud.ValidatePath,
},
"file_content_hash": {
Description: "Hash value of the script file content. Used to detect changes.",
Type: schema.TypeString,
Required: true,
},
"substitutions": {
Description: "A substitution is a key value pair where the key is the value you want to replace, and the value is the value to substitute in its place.",
Type: schema.TypeMap,
Optional: true,
},
},
}
}
// ExporterScript returns all the exporter configuration for this resource
func ExporterScript() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllScripts),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{},
CustomFileWriter: resourceExporter.CustomFileWriterSettings{
RetrieveAndWriteFilesFunc: ScriptResolver,
SubDirectory: "scripts",
},
}
}
package station
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
util "terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceStationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
sp := getStationProxy(sdkConfig)
stationName := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
stationId, retryable, resp, err := sp.getStationIdByName(ctx, stationName)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting station %s", err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no stations found"), resp))
}
d.SetId(stationId)
return nil
})
}
package station
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *stationProxy
type getStationIdByNameFunc func(ctx context.Context, p *stationProxy, stationName string) (stationId string, retryable bool, resp *platformclientv2.APIResponse, err error)
// stationProxy contains all of the methods that call genesys cloud APIs.
type stationProxy struct {
clientConfig *platformclientv2.Configuration
stationsApi *platformclientv2.StationsApi
getStationIdByNameAttr getStationIdByNameFunc
}
// newStationProxy initializes the Station proxy with all of the data needed to communicate with Genesys Cloud
func newStationProxy(clientConfig *platformclientv2.Configuration) *stationProxy {
stationsApi := platformclientv2.NewStationsApiWithConfig(clientConfig)
return &stationProxy{
clientConfig: clientConfig,
stationsApi: stationsApi,
getStationIdByNameAttr: getStationIdByNameFn,
}
}
// getStationProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getStationProxy(clientConfig *platformclientv2.Configuration) *stationProxy {
if internalProxy == nil {
internalProxy = newStationProxy(clientConfig)
}
return internalProxy
}
// getStationIdByName retrieves a Genesys Cloud Station ID by Name
func (p *stationProxy) getStationIdByName(ctx context.Context, stationName string) (stationId string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getStationIdByNameAttr(ctx, p, stationName)
}
// getStationIdByNameFn is an implementation function for retrieving a Station Id by Name
func getStationIdByNameFn(ctx context.Context, p *stationProxy, stationName string) (stationId string, retryable bool, resp *platformclientv2.APIResponse, err error) {
const pageSize = 100
stations, resp, err := p.stationsApi.GetStations(pageSize, 1, "", stationName, "", "", "", "")
if err != nil {
return "", false, resp, err
}
if stations.Entities == nil || len(*stations.Entities) == 0 {
return "", true, resp, fmt.Errorf("failed to find ID of station '%s'", stationName)
}
for _, station := range *stations.Entities {
if *station.Name == stationName {
return *station.Id, false, resp, nil
}
}
for pageNum := 2; pageNum <= *stations.PageCount; pageNum++ {
stations, resp, err := p.stationsApi.GetStations(pageSize, pageNum, "", stationName, "", "", "", "")
if err != nil {
return "", false, resp, err
}
if stations.Entities == nil {
return "", true, resp, fmt.Errorf("failed to find ID of station '%s'", stationName)
}
for _, station := range *stations.Entities {
if *station.Name == stationName {
return *station.Id, false, resp, nil
}
}
}
return "", true, resp, fmt.Errorf("failed to find ID of station '%s'", stationName)
}
package station
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_station"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceStation())
}
// DataSourceStation registers the genesyscloud_station data source
func DataSourceStation() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Stations. Select a station by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceStationRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Station name.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
package task_management_workbin
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_task_management_workbin.go contains the data source implementation
for the resource.
*/
// dataSourceTaskManagementWorkbinRead retrieves by name the id in question
func dataSourceTaskManagementWorkbinRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkbinProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
workbinId, retryable, resp, err := proxy.getTaskManagementWorkbinIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error searching task management workbin %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no task management workbin found with name %s", name), resp))
}
d.SetId(workbinId)
return nil
})
}
package task_management_workbin
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_task_management_workbin_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *taskManagementWorkbinProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createTaskManagementWorkbinFunc func(ctx context.Context, p *taskManagementWorkbinProxy, workbin *platformclientv2.Workbincreate) (*platformclientv2.Workbin, *platformclientv2.APIResponse, error)
type getAllTaskManagementWorkbinFunc func(ctx context.Context, p *taskManagementWorkbinProxy) (*[]platformclientv2.Workbin, *platformclientv2.APIResponse, error)
type getTaskManagementWorkbinIdByNameFunc func(ctx context.Context, p *taskManagementWorkbinProxy, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
type getTaskManagementWorkbinByIdFunc func(ctx context.Context, p *taskManagementWorkbinProxy, id string) (workbin *platformclientv2.Workbin, response *platformclientv2.APIResponse, err error)
type updateTaskManagementWorkbinFunc func(ctx context.Context, p *taskManagementWorkbinProxy, id string, workbin *platformclientv2.Workbinupdate) (*platformclientv2.Workbin, *platformclientv2.APIResponse, error)
type deleteTaskManagementWorkbinFunc func(ctx context.Context, p *taskManagementWorkbinProxy, id string) (response *platformclientv2.APIResponse, err error)
// taskManagementWorkbinProxy contains all of the methods that call genesys cloud APIs.
type taskManagementWorkbinProxy struct {
clientConfig *platformclientv2.Configuration
taskManagementApi *platformclientv2.TaskManagementApi
createTaskManagementWorkbinAttr createTaskManagementWorkbinFunc
getAllTaskManagementWorkbinAttr getAllTaskManagementWorkbinFunc
getTaskManagementWorkbinIdByNameAttr getTaskManagementWorkbinIdByNameFunc
getTaskManagementWorkbinByIdAttr getTaskManagementWorkbinByIdFunc
updateTaskManagementWorkbinAttr updateTaskManagementWorkbinFunc
deleteTaskManagementWorkbinAttr deleteTaskManagementWorkbinFunc
}
// newTaskManagementWorkbinProxy initializes the task management workbin proxy with all of the data needed to communicate with Genesys Cloud
func newTaskManagementWorkbinProxy(clientConfig *platformclientv2.Configuration) *taskManagementWorkbinProxy {
api := platformclientv2.NewTaskManagementApiWithConfig(clientConfig)
return &taskManagementWorkbinProxy{
clientConfig: clientConfig,
taskManagementApi: api,
createTaskManagementWorkbinAttr: createTaskManagementWorkbinFn,
getAllTaskManagementWorkbinAttr: getAllTaskManagementWorkbinFn,
getTaskManagementWorkbinIdByNameAttr: getTaskManagementWorkbinIdByNameFn,
getTaskManagementWorkbinByIdAttr: getTaskManagementWorkbinByIdFn,
updateTaskManagementWorkbinAttr: updateTaskManagementWorkbinFn,
deleteTaskManagementWorkbinAttr: deleteTaskManagementWorkbinFn,
}
}
// getTaskManagementWorkbinProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTaskManagementWorkbinProxy(clientConfig *platformclientv2.Configuration) *taskManagementWorkbinProxy {
if internalProxy == nil {
internalProxy = newTaskManagementWorkbinProxy(clientConfig)
}
return internalProxy
}
// createTaskManagementWorkbin creates a Genesys Cloud task management workbin
func (p *taskManagementWorkbinProxy) createTaskManagementWorkbin(ctx context.Context, taskManagementWorkbin *platformclientv2.Workbincreate) (*platformclientv2.Workbin, *platformclientv2.APIResponse, error) {
return p.createTaskManagementWorkbinAttr(ctx, p, taskManagementWorkbin)
}
// getTaskManagementWorkbin retrieves all Genesys Cloud task management workbin
func (p *taskManagementWorkbinProxy) getAllTaskManagementWorkbin(ctx context.Context) (*[]platformclientv2.Workbin, *platformclientv2.APIResponse, error) {
return p.getAllTaskManagementWorkbinAttr(ctx, p)
}
// getTaskManagementWorkbinIdByName returns a single Genesys Cloud task management workbin by a name
func (p *taskManagementWorkbinProxy) getTaskManagementWorkbinIdByName(ctx context.Context, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorkbinIdByNameAttr(ctx, p, name)
}
// getTaskManagementWorkbinById returns a single Genesys Cloud task management workbin by Id
func (p *taskManagementWorkbinProxy) getTaskManagementWorkbinById(ctx context.Context, id string) (taskManagementWorkbin *platformclientv2.Workbin, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorkbinByIdAttr(ctx, p, id)
}
// updateTaskManagementWorkbin updates a Genesys Cloud task management workbin
func (p *taskManagementWorkbinProxy) updateTaskManagementWorkbin(ctx context.Context, id string, taskManagementWorkbin *platformclientv2.Workbinupdate) (*platformclientv2.Workbin, *platformclientv2.APIResponse, error) {
return p.updateTaskManagementWorkbinAttr(ctx, p, id, taskManagementWorkbin)
}
// deleteTaskManagementWorkbin deletes a Genesys Cloud task management workbin by Id
func (p *taskManagementWorkbinProxy) deleteTaskManagementWorkbin(ctx context.Context, id string) (resp *platformclientv2.APIResponse, err error) {
return p.deleteTaskManagementWorkbinAttr(ctx, p, id)
}
// createTaskManagementWorkbinFn is an implementation function for creating a Genesys Cloud task management workbin
func createTaskManagementWorkbinFn(ctx context.Context, p *taskManagementWorkbinProxy, taskManagementWorkbin *platformclientv2.Workbincreate) (*platformclientv2.Workbin, *platformclientv2.APIResponse, error) {
workbin, resp, err := p.taskManagementApi.PostTaskmanagementWorkbins(*taskManagementWorkbin)
if err != nil {
return nil, resp, fmt.Errorf("failed to create task management workbin: %s", err)
}
return workbin, resp, nil
}
// getAllTaskManagementWorkbinFn is the implementation for retrieving all task management workbin in Genesys Cloud
func getAllTaskManagementWorkbinFn(ctx context.Context, p *taskManagementWorkbinProxy) (*[]platformclientv2.Workbin, *platformclientv2.APIResponse, error) {
var allWorkbins []platformclientv2.Workbin
pageSize := 200
after := ""
var response *platformclientv2.APIResponse
for {
queryReq := &platformclientv2.Workbinqueryrequest{
PageSize: &pageSize,
After: &after,
}
workbins, resp, err := p.taskManagementApi.PostTaskmanagementWorkbinsQuery(*queryReq)
if err != nil {
return nil, resp, fmt.Errorf("failed to get workbin: %v", err)
}
response = resp
allWorkbins = append(allWorkbins, *workbins.Entities...)
// Exit loop if there are no more 'pages'
if workbins.After == nil || *workbins.After == "" {
break
}
after = *workbins.After
}
return &allWorkbins, response, nil
}
// getTaskManagementWorkbinIdByNameFn is an implementation of the function to get a Genesys Cloud task management workbin by name
func getTaskManagementWorkbinIdByNameFn(ctx context.Context, p *taskManagementWorkbinProxy, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
workbins, resp, err := p.getAllTaskManagementWorkbin(ctx)
if err != nil {
return "", false, resp, fmt.Errorf("failed to get workbin %s. failed to get all task management workbins", name)
}
for _, workbin := range *workbins {
if *workbin.Name == name {
return *workbin.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("no task management workbin found with name %s", name)
}
// getTaskManagementWorkbinByIdFn is an implementation of the function to get a Genesys Cloud task management workbin by Id
func getTaskManagementWorkbinByIdFn(ctx context.Context, p *taskManagementWorkbinProxy, id string) (taskManagementWorkbin *platformclientv2.Workbin, resp *platformclientv2.APIResponse, err error) {
workbin, resp, err := p.taskManagementApi.GetTaskmanagementWorkbin(id)
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve task management workbin by id %s: %s", id, err)
}
return workbin, resp, nil
}
// updateTaskManagementWorkbinFn is an implementation of the function to update a Genesys Cloud task management workbin
func updateTaskManagementWorkbinFn(ctx context.Context, p *taskManagementWorkbinProxy, id string, taskManagementWorkbin *platformclientv2.Workbinupdate) (*platformclientv2.Workbin, *platformclientv2.APIResponse, error) {
workbin, resp, err := p.taskManagementApi.PatchTaskmanagementWorkbin(id, *taskManagementWorkbin)
if err != nil {
return nil, resp, fmt.Errorf("failed to update task management workbin: %s", err)
}
return workbin, resp, nil
}
// deleteTaskManagementWorkbinFn is an implementation function for deleting a Genesys Cloud task management workbin
func deleteTaskManagementWorkbinFn(ctx context.Context, p *taskManagementWorkbinProxy, id string) (resp *platformclientv2.APIResponse, err error) {
resp, err = p.taskManagementApi.DeleteTaskmanagementWorkbin(id)
if err != nil {
return resp, fmt.Errorf("failed to delete task management workbin: %s", err)
}
return resp, nil
}
package task_management_workbin
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_task_management_workbin.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthTaskManagementWorkbin retrieves all of the task management workbin via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthTaskManagementWorkbins(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getTaskManagementWorkbinProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
workbins, resp, err := proxy.getAllTaskManagementWorkbin(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get all workbins error: %s", err), resp)
}
for _, workbin := range *workbins {
log.Printf("Dealing with task management workbin id: %s", *workbin.Id)
resources[*workbin.Id] = &resourceExporter.ResourceMeta{Name: *workbin.Name}
}
return resources, nil
}
// createTaskManagementWorkbin is used by the task_management_workbin resource to create Genesys cloud task management workbin
func createTaskManagementWorkbin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkbinProxy(sdkConfig)
taskManagementWorkbin := platformclientv2.Workbincreate{
Name: platformclientv2.String(d.Get("name").(string)),
DivisionId: platformclientv2.String(d.Get("division_id").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
}
log.Printf("Creating task management workbin %s", *taskManagementWorkbin.Name)
workbin, resp, err := proxy.createTaskManagementWorkbin(ctx, &taskManagementWorkbin)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create task management workbin %s error: %s", *taskManagementWorkbin.Name, err), resp)
}
d.SetId(*workbin.Id)
log.Printf("Created task management workbin %s", *workbin.Id)
return readTaskManagementWorkbin(ctx, d, meta)
}
// readTaskManagementWorkbin is used by the task_management_workbin resource to read an task management workbin from genesys cloud
func readTaskManagementWorkbin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkbinProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTaskManagementWorkbin(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading task management workbin %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
workbin, resp, getErr := proxy.getTaskManagementWorkbinById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management workbin %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management workbin %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", workbin.Name)
resourcedata.SetNillableReferenceDivision(d, "division_id", workbin.Division)
resourcedata.SetNillableValue(d, "description", workbin.Description)
log.Printf("Read task management workbin %s %s", d.Id(), *workbin.Name)
return cc.CheckState(d)
})
}
// updateTaskManagementWorkbin is used by the task_management_workbin resource to update an task management workbin in Genesys Cloud
func updateTaskManagementWorkbin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkbinProxy(sdkConfig)
taskManagementWorkbin := platformclientv2.Workbinupdate{
Name: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
}
log.Printf("Updating task management workbin %s", *taskManagementWorkbin.Name)
workbin, resp, err := proxy.updateTaskManagementWorkbin(ctx, d.Id(), &taskManagementWorkbin)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update task management workbin %s error: %s", *taskManagementWorkbin.Name, err), resp)
}
log.Printf("Updated task management workbin %s", *workbin.Id)
return readTaskManagementWorkbin(ctx, d, meta)
}
// deleteTaskManagementWorkbin is used by the task_management_workbin resource to delete an task management workbin from Genesys cloud
func deleteTaskManagementWorkbin(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkbinProxy(sdkConfig)
resp, err := proxy.deleteTaskManagementWorkbin(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete task management workbin %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getTaskManagementWorkbinById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted task management workbin %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting task management workbin %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("task management workbin %s still exists", d.Id()), resp))
})
}
package task_management_workbin
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_task_management_workbin_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the task_management_workbin resource.
3. The datasource schema definitions for the task_management_workbin datasource.
4. The resource exporter configuration for the task_management_workbin exporter.
*/
const resourceName = "genesyscloud_task_management_workbin"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceTaskManagementWorkbin())
regInstance.RegisterDataSource(resourceName, DataSourceTaskManagementWorkbin())
regInstance.RegisterExporter(resourceName, TaskManagementWorkbinExporter())
}
// ResourceTaskManagementWorkbin registers the genesyscloud_task_management_workbin resource with Terraform
func ResourceTaskManagementWorkbin() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud task management workbin`,
CreateContext: provider.CreateWithPooledClient(createTaskManagementWorkbin),
ReadContext: provider.ReadWithPooledClient(readTaskManagementWorkbin),
UpdateContext: provider.UpdateWithPooledClient(updateTaskManagementWorkbin),
DeleteContext: provider.DeleteWithPooledClient(deleteTaskManagementWorkbin),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Workbin name",
Required: true,
Type: schema.TypeString,
},
"division_id": {
Description: "The division to which this entity belongs.",
Optional: true,
Computed: true,
Type: schema.TypeString,
ForceNew: true,
},
"description": {
Description: "Workbin description",
Optional: true,
Type: schema.TypeString,
},
},
}
}
// TaskManagementWorkbinExporter returns the resourceExporter object used to hold the genesyscloud_task_management_workbin exporter's config
func TaskManagementWorkbinExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthTaskManagementWorkbins),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
},
}
}
// DataSourceTaskManagementWorkbin registers the genesyscloud_task_management_workbin data source
func DataSourceTaskManagementWorkbin() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud task management workbin data source. Select an task management workbin by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceTaskManagementWorkbinRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `task management workbin name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package task_management_workbin
import (
"fmt"
)
// GenerateWorkbinResource is a public util method to generate a workbin terraform resource for testing
func GenerateWorkbinResource(resourceId string, name string, description string, divisionIdRef string) string {
return fmt.Sprintf(`resource "%s" "%s" {
name = "%s"
description = "%s"
division_id = %s
}
`, resourceName, resourceId, name, description, divisionIdRef)
}
package task_management_workitem
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_task_management_workitem.go contains the data source implementation
for the resource.
*/
// dataSourceTaskManagementWorkitemRead retrieves by name the id in question
func dataSourceTaskManagementWorkitemRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkitemProxy(sdkConfig)
name := d.Get("name").(string)
workbinId := d.Get("workbin_id").(string)
worktypeId := d.Get("worktype_id").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
workitemId, retryable, resp, err := proxy.getTaskManagementWorkitemIdByName(ctx, name, workbinId, worktypeId)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error searching task management workitem %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no task management workitem found with name %s", name), resp))
}
d.SetId(workitemId)
return nil
})
}
package task_management_workitem
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_task_management_workitem_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *taskManagementWorkitemProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createTaskManagementWorkitemFunc func(ctx context.Context, p *taskManagementWorkitemProxy, workitem *platformclientv2.Workitemcreate) (*platformclientv2.Workitem, *platformclientv2.APIResponse, error)
type getAllTaskManagementWorkitemFunc func(ctx context.Context, p *taskManagementWorkitemProxy) (*[]platformclientv2.Workitem, *platformclientv2.APIResponse, error)
type getTaskManagementWorkitemIdByNameFunc func(ctx context.Context, p *taskManagementWorkitemProxy, name string, workbinId string, worktypeId string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
type getTaskManagementWorkitemByIdFunc func(ctx context.Context, p *taskManagementWorkitemProxy, id string) (workitem *platformclientv2.Workitem, response *platformclientv2.APIResponse, err error)
type updateTaskManagementWorkitemFunc func(ctx context.Context, p *taskManagementWorkitemProxy, id string, workitem *platformclientv2.Workitemupdate) (*platformclientv2.Workitem, *platformclientv2.APIResponse, error)
type deleteTaskManagementWorkitemFunc func(ctx context.Context, p *taskManagementWorkitemProxy, id string) (response *platformclientv2.APIResponse, err error)
// taskManagementWorkitemProxy contains all of the methods that call genesys cloud APIs.
type taskManagementWorkitemProxy struct {
clientConfig *platformclientv2.Configuration
taskManagementApi *platformclientv2.TaskManagementApi
createTaskManagementWorkitemAttr createTaskManagementWorkitemFunc
getAllTaskManagementWorkitemAttr getAllTaskManagementWorkitemFunc
getTaskManagementWorkitemIdByNameAttr getTaskManagementWorkitemIdByNameFunc
getTaskManagementWorkitemByIdAttr getTaskManagementWorkitemByIdFunc
updateTaskManagementWorkitemAttr updateTaskManagementWorkitemFunc
deleteTaskManagementWorkitemAttr deleteTaskManagementWorkitemFunc
}
// newTaskManagementWorkitemProxy initializes the task management workitem proxy with all of the data needed to communicate with Genesys Cloud
func newTaskManagementWorkitemProxy(clientConfig *platformclientv2.Configuration) *taskManagementWorkitemProxy {
api := platformclientv2.NewTaskManagementApiWithConfig(clientConfig)
return &taskManagementWorkitemProxy{
clientConfig: clientConfig,
taskManagementApi: api,
createTaskManagementWorkitemAttr: createTaskManagementWorkitemFn,
getAllTaskManagementWorkitemAttr: getAllTaskManagementWorkitemFn,
getTaskManagementWorkitemIdByNameAttr: getTaskManagementWorkitemIdByNameFn,
getTaskManagementWorkitemByIdAttr: getTaskManagementWorkitemByIdFn,
updateTaskManagementWorkitemAttr: updateTaskManagementWorkitemFn,
deleteTaskManagementWorkitemAttr: deleteTaskManagementWorkitemFn,
}
}
// getTaskManagementWorkitemProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTaskManagementWorkitemProxy(clientConfig *platformclientv2.Configuration) *taskManagementWorkitemProxy {
if internalProxy == nil {
internalProxy = newTaskManagementWorkitemProxy(clientConfig)
}
return internalProxy
}
// createTaskManagementWorkitem creates a Genesys Cloud task management workitem
func (p *taskManagementWorkitemProxy) createTaskManagementWorkitem(ctx context.Context, taskManagementWorkitem *platformclientv2.Workitemcreate) (*platformclientv2.Workitem, *platformclientv2.APIResponse, error) {
return p.createTaskManagementWorkitemAttr(ctx, p, taskManagementWorkitem)
}
// getTaskManagementWorkitem retrieves all Genesys Cloud task management workitem
func (p *taskManagementWorkitemProxy) getAllTaskManagementWorkitem(ctx context.Context) (*[]platformclientv2.Workitem, *platformclientv2.APIResponse, error) {
return p.getAllTaskManagementWorkitemAttr(ctx, p)
}
// getTaskManagementWorkitemIdByName returns a single Genesys Cloud task management workitem by a name
func (p *taskManagementWorkitemProxy) getTaskManagementWorkitemIdByName(ctx context.Context, name string, workbinId string, worktypeId string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorkitemIdByNameAttr(ctx, p, name, workbinId, worktypeId)
}
// getTaskManagementWorkitemById returns a single Genesys Cloud task management workitem by Id
func (p *taskManagementWorkitemProxy) getTaskManagementWorkitemById(ctx context.Context, id string) (taskManagementWorkitem *platformclientv2.Workitem, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorkitemByIdAttr(ctx, p, id)
}
// updateTaskManagementWorkitem updates a Genesys Cloud task management workitem
func (p *taskManagementWorkitemProxy) updateTaskManagementWorkitem(ctx context.Context, id string, taskManagementWorkitem *platformclientv2.Workitemupdate) (*platformclientv2.Workitem, *platformclientv2.APIResponse, error) {
return p.updateTaskManagementWorkitemAttr(ctx, p, id, taskManagementWorkitem)
}
// deleteTaskManagementWorkitem deletes a Genesys Cloud task management workitem by Id
func (p *taskManagementWorkitemProxy) deleteTaskManagementWorkitem(ctx context.Context, id string) (resp *platformclientv2.APIResponse, err error) {
return p.deleteTaskManagementWorkitemAttr(ctx, p, id)
}
// createTaskManagementWorkitemFn is an implementation function for creating a Genesys Cloud task management workitem
func createTaskManagementWorkitemFn(ctx context.Context, p *taskManagementWorkitemProxy, taskManagementWorkitem *platformclientv2.Workitemcreate) (*platformclientv2.Workitem, *platformclientv2.APIResponse, error) {
log.Printf("Creating task management worktype: %s", *taskManagementWorkitem.Name)
workitem, resp, err := p.taskManagementApi.PostTaskmanagementWorkitems(*taskManagementWorkitem)
log.Printf("Completed call to create task management worktype %s with status code %d, correlation id %s and err %s", *taskManagementWorkitem.Name, resp.StatusCode, resp.CorrelationID, err)
if err != nil {
return nil, resp, fmt.Errorf("failed to create task management workitem: %s", err)
}
return workitem, resp, nil
}
// getAllTaskManagementWorkitemFn is the implementation for retrieving all task management workitem in Genesys Cloud
func getAllTaskManagementWorkitemFn(ctx context.Context, p *taskManagementWorkitemProxy) (*[]platformclientv2.Workitem, *platformclientv2.APIResponse, error) {
// Workitem query requires one of workbin, assignee, or worktype filter. We'll use workbins.
// Get all workbins
var allWorkbins []platformclientv2.Workbin
pageSize := 200
after := ""
var response *platformclientv2.APIResponse
for {
queryReq := &platformclientv2.Workbinqueryrequest{
PageSize: &pageSize,
After: &after,
}
workbins, resp, err := p.taskManagementApi.PostTaskmanagementWorkbinsQuery(*queryReq)
response = resp
if err != nil {
return nil, resp, fmt.Errorf("failed to get workbin: %v", err)
}
allWorkbins = append(allWorkbins, *workbins.Entities...)
// Exit loop if there are no more 'pages'
if workbins.After == nil || *workbins.After == "" {
break
}
after = *workbins.After
}
// Method to query workitems on a workbin
queryWorkitemsOnWorkbin := func(workbinId string) (*[]platformclientv2.Workitem, error) {
var wbWorkitems []platformclientv2.Workitem
pageSize := 200
after := ""
for {
queryReq := &platformclientv2.Workitemquerypostrequest{
PageSize: &pageSize,
After: &after,
Filters: &[]platformclientv2.Workitemfilter{
{
Name: platformclientv2.String("workbinId"),
VarType: platformclientv2.String("String"),
Operator: platformclientv2.String("EQ"),
Values: &[]string{workbinId},
},
},
}
workitems, resp, err := p.taskManagementApi.PostTaskmanagementWorkitemsQuery(*queryReq)
response = resp
if err != nil {
return nil, fmt.Errorf("failed to get workitems: %v %v", err, resp)
}
wbWorkitems = append(wbWorkitems, *workitems.Entities...)
// Exit loop if there are no more 'pages'
if workitems.After == nil || *workitems.After == "" {
break
}
after = *workitems.After
}
return &wbWorkitems, nil
}
// Get all workitems on all workbins
var allWorkitems []platformclientv2.Workitem
for _, wb := range allWorkbins {
wbWorkitems, err := queryWorkitemsOnWorkbin(*wb.Id)
if err != nil {
return nil, response, fmt.Errorf("failed to get workitems on workbin %s: %v", *wb.Id, err)
}
allWorkitems = append(allWorkitems, *wbWorkitems...)
}
return &allWorkitems, response, nil
}
// getTaskManagementWorkitemIdByNameFn is an implementation of the function to get a Genesys Cloud task management workitem by name
func getTaskManagementWorkitemIdByNameFn(ctx context.Context, p *taskManagementWorkitemProxy, name string, workbinId string, worktypeId string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
pageSize := 100
// Filter for the workitem name
queryReq := &platformclientv2.Workitemquerypostrequest{
PageSize: &pageSize,
Filters: &[]platformclientv2.Workitemfilter{
{
Name: platformclientv2.String("name"),
VarType: platformclientv2.String("String"),
Operator: platformclientv2.String("EQ"),
Values: &[]string{name},
},
},
}
// Filter for the worktype id
if worktypeId != "" {
*queryReq.Filters = append(*queryReq.Filters, platformclientv2.Workitemfilter{
Name: platformclientv2.String("typeId"),
VarType: platformclientv2.String("String"),
Operator: platformclientv2.String("EQ"),
Values: &[]string{worktypeId},
})
}
// Filter for the workbin id
if workbinId != "" {
*queryReq.Filters = append(*queryReq.Filters, platformclientv2.Workitemfilter{
Name: platformclientv2.String("workbinId"),
VarType: platformclientv2.String("String"),
Operator: platformclientv2.String("EQ"),
Values: &[]string{workbinId},
})
}
workitems, resp, err := p.taskManagementApi.PostTaskmanagementWorkitemsQuery(*queryReq)
if err != nil {
return "", false, resp, fmt.Errorf("failed to get worktype %s: %v", name, err)
}
if workitems.Entities == nil || len(*workitems.Entities) == 0 {
return "", true, resp, fmt.Errorf("no task management worktype found with name %s", name)
}
workitem := (*workitems.Entities)[0]
log.Printf("Retrieved the task management worktype id %s by name %s", *workitem.Id, name)
return *workitem.Id, false, resp, nil
}
// getTaskManagementWorkitemByIdFn is an implementation of the function to get a Genesys Cloud task management workitem by Id
func getTaskManagementWorkitemByIdFn(ctx context.Context, p *taskManagementWorkitemProxy, id string) (taskManagementWorkitem *platformclientv2.Workitem, resp *platformclientv2.APIResponse, err error) {
workitem, resp, err := p.taskManagementApi.GetTaskmanagementWorkitem(id, "")
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve task management workitem by id %s: %s", id, err)
}
return workitem, resp, nil
}
// updateTaskManagementWorkitemFn is an implementation of the function to update a Genesys Cloud task management workitem
func updateTaskManagementWorkitemFn(ctx context.Context, p *taskManagementWorkitemProxy, id string, taskManagementWorkitem *platformclientv2.Workitemupdate) (*platformclientv2.Workitem, *platformclientv2.APIResponse, error) {
workitem, resp, err := p.taskManagementApi.PatchTaskmanagementWorkitem(id, *taskManagementWorkitem)
if err != nil {
return nil, resp, fmt.Errorf("failed to update task management workitem: %s", err)
}
return workitem, resp, nil
}
// deleteTaskManagementWorkitemFn is an implementation function for deleting a Genesys Cloud task management workitem
func deleteTaskManagementWorkitemFn(ctx context.Context, p *taskManagementWorkitemProxy, id string) (resp *platformclientv2.APIResponse, err error) {
resp, err = p.taskManagementApi.DeleteTaskmanagementWorkitem(id)
if err != nil {
return resp, fmt.Errorf("failed to delete task management workitem: %s", err)
}
return resp, nil
}
package task_management_workitem
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_task_management_workitem.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthTaskManagementWorkitem retrieves all of the task management workitem via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthTaskManagementWorkitems(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getTaskManagementWorkitemProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
workitems, resp, err := proxy.getAllTaskManagementWorkitem(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get task management workitem error: %s", err), resp)
}
for _, workitem := range *workitems {
resources[*workitem.Id] = &resourceExporter.ResourceMeta{Name: *workitem.Name}
}
return resources, nil
}
// createTaskManagementWorkitem is used by the task_management_workitem resource to create Genesys cloud task management workitem
func createTaskManagementWorkitem(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkitemProxy(sdkConfig)
taskManagementWorkitem, err := getWorkitemCreateFromResourceData(d)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to build Workitem create from resource data"), err)
}
log.Printf("Creating task management workitem %s", *taskManagementWorkitem.Name)
workitem, resp, err := proxy.createTaskManagementWorkitem(ctx, taskManagementWorkitem)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create task management workitem %s error: %s", *taskManagementWorkitem.Name, err), resp)
}
d.SetId(*workitem.Id)
log.Printf("Created task management workitem %s", *workitem.Id)
return readTaskManagementWorkitem(ctx, d, meta)
}
// readTaskManagementWorkitem is used by the task_management_workitem resource to read an task management workitem from genesys cloud
func readTaskManagementWorkitem(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkitemProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTaskManagementWorkitem(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading task management workitem %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
workitem, resp, getErr := proxy.getTaskManagementWorkitemById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management workitem %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management workitem %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", workitem.Name)
resourcedata.SetNillableValue(d, "description", workitem.Description)
resourcedata.SetNillableValue(d, "priority", workitem.Priority)
resourcedata.SetNillableTime(d, "date_due", workitem.DateDue)
resourcedata.SetNillableTime(d, "date_expires", workitem.DateExpires)
resourcedata.SetNillableValue(d, "duration_seconds", workitem.DurationSeconds)
resourcedata.SetNillableValue(d, "ttl", workitem.Ttl)
resourcedata.SetNillableValue(d, "external_tag", workitem.ExternalTag)
resourcedata.SetNillableValue(d, "auto_status_transition", workitem.AutoStatusTransition)
if workitem.VarType != nil {
resourcedata.SetNillableValue(d, "worktype_id", workitem.VarType.Id)
}
if workitem.Language != nil {
resourcedata.SetNillableValue(d, "language_id", workitem.Language.Id)
}
if workitem.Status != nil {
resourcedata.SetNillableValue(d, "status_id", workitem.Status.Id)
}
if workitem.Workbin != nil {
resourcedata.SetNillableValue(d, "workbin_id", workitem.Workbin.Id)
}
if workitem.Assignee != nil {
resourcedata.SetNillableValue(d, "assignee_id", workitem.Assignee.Id)
}
if workitem.ExternalContact != nil {
resourcedata.SetNillableValue(d, "external_contact_id", workitem.ExternalContact.Id)
}
if workitem.Queue != nil {
resourcedata.SetNillableValue(d, "queue_id", workitem.Queue.Id)
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "skills_ids", workitem.Skills, flattenRoutingSkillReferences)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "preferred_agents_ids", workitem.PreferredAgents, flattenUserReferences)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "scored_agents", workitem.ScoredAgents, flattenWorkitemScoredAgents)
if workitem.CustomFields != nil {
cf, err := flattenCustomFields(workitem.CustomFields)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("failed to flatten custom fields: %v", err))
}
d.Set("custom_fields", cf)
} else {
d.Set("custom_fields", "")
}
log.Printf("Read task management workitem %s %s", d.Id(), *workitem.Name)
return cc.CheckState(d)
})
}
// updateTaskManagementWorkitem is used by the task_management_workitem resource to update an task management workitem in Genesys Cloud
func updateTaskManagementWorkitem(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkitemProxy(sdkConfig)
taskManagementWorkitem, err := getWorkitemUpdateFromResourceData(d)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to update Workitem create from resource data"), err)
}
log.Printf("Updating task management workitem %s", *taskManagementWorkitem.Name)
workitem, resp, err := proxy.updateTaskManagementWorkitem(ctx, d.Id(), taskManagementWorkitem)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update task management workitem %s error: %s", *taskManagementWorkitem.Name, err), resp)
}
log.Printf("Updated task management workitem %s", *workitem.Id)
return readTaskManagementWorkitem(ctx, d, meta)
}
// deleteTaskManagementWorkitem is used by the task_management_workitem resource to delete an task management workitem from Genesys cloud
func deleteTaskManagementWorkitem(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorkitemProxy(sdkConfig)
resp, err := proxy.deleteTaskManagementWorkitem(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete task management workitem %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getTaskManagementWorkitemById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted task management workitem %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting task management workitem %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("task management workitem %s still exists", d.Id()), resp))
})
}
package task_management_workitem
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
/*
resource_genesycloud_task_management_workitem_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the task_management_workitem resource.
3. The datasource schema definitions for the task_management_workitem datasource.
4. The resource exporter configuration for the task_management_workitem exporter.
*/
const resourceName = "genesyscloud_task_management_workitem"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceTaskManagementWorkitem())
regInstance.RegisterDataSource(resourceName, DataSourceTaskManagementWorkitem())
regInstance.RegisterExporter(resourceName, TaskManagementWorkitemExporter())
}
// ResourceTaskManagementWorkitem registers the genesyscloud_task_management_workitem resource with Terraform
func ResourceTaskManagementWorkitem() *schema.Resource {
workitemScoredAgentResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`agent_id`: {
Description: `The agent id`,
Required: true,
Type: schema.TypeString,
},
`score`: {
Description: `Agent's score for the workitem, from 0 - 100, higher being better`,
Required: true,
Type: schema.TypeInt,
ValidateFunc: validation.IntBetween(0, 100),
},
},
}
return &schema.Resource{
Description: `Genesys Cloud task management workitem`,
CreateContext: provider.CreateWithPooledClient(createTaskManagementWorkitem),
ReadContext: provider.ReadWithPooledClient(readTaskManagementWorkitem),
UpdateContext: provider.UpdateWithPooledClient(updateTaskManagementWorkitem),
DeleteContext: provider.DeleteWithPooledClient(deleteTaskManagementWorkitem),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the Workitem.`,
Required: true,
Type: schema.TypeString,
},
`worktype_id`: {
Description: `The Worktype ID of the Workitem.`,
Required: true,
ForceNew: true,
Type: schema.TypeString,
},
`description`: {
Description: `The description of the Workitem.`,
Optional: true,
Type: schema.TypeString,
},
`language_id`: {
Description: `The language of the Workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`priority`: {
Description: `The priority of the Workitem. The valid range is between -25,000,000 and 25,000,000.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
ValidateFunc: validation.IntBetween(-25000000, 25000000),
},
`date_due`: {
Description: `The due date of the Workitem. Date time is represented as an ISO-8601 string. For example: yyyy-MM-ddTHH:mm:ss[.mmm]Z`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateDiagFunc: gcloud.ValidateLocalDateTimes,
},
`date_expires`: {
Description: `The expiry date of the Workitem. Date time is represented as an ISO-8601 string. For example: yyyy-MM-ddTHH:mm:ss[.mmm]Z`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateDiagFunc: gcloud.ValidateLocalDateTimes,
},
`duration_seconds`: {
Description: `The estimated duration in seconds to complete the workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`ttl`: {
Description: `The time to live of the Workitem in seconds.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`status_id`: {
Description: `The id of the current status of the Workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`workbin_id`: {
Description: `The id of the Workbin that contains the Workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`assignee_id`: {
Description: `The id of the assignee of the Workitem.`,
Optional: true,
Type: schema.TypeString,
},
`external_contact_id`: {
Description: `The id of the external contact of the Workitem.`,
Optional: true,
Type: schema.TypeString,
},
`external_tag`: {
Description: `The external tag of the Workitem.`,
Optional: true,
Type: schema.TypeString,
},
`queue_id`: {
Description: `The Workitem's queue id.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`skills_ids`: {
Description: `The ids of skills of the Workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`preferred_agents_ids`: {
Description: `Ids of the preferred agents of the Workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`auto_status_transition`: {
Description: `Set it to false to disable auto status transition. By default, it is enabled.`,
Optional: true,
Computed: true,
Type: schema.TypeBool,
},
`scored_agents`: {
Description: `A list of scored agents for the Workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeList,
MaxItems: 20,
Elem: workitemScoredAgentResource,
},
`custom_fields`: {
Description: `JSON formatted object for custom field values defined in the schema referenced by the worktype of the workitem.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
},
}
}
// TaskManagementWorkitemExporter returns the resourceExporter object used to hold the genesyscloud_task_management_workitem exporter's config
func TaskManagementWorkitemExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthTaskManagementWorkitems),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"worktype_id": {RefType: "genesyscloud_task_management_worktype"},
"language_id": {RefType: "genesyscloud_routing_language"},
"workbin_id": {RefType: "genesyscloud_task_management_workbin"},
"assignee_id": {RefType: "genesyscloud_user"},
"preferred_agents_ids": {RefType: "genesyscloud_user"},
"scored_agents.agent_id": {RefType: "genesyscloud_user"},
"external_contact_id": {RefType: "genesyscloud_externalcontacts_contact"},
"queue_id": {RefType: "genesyscloud_routing_queue"},
"skills_ids": {RefType: "genesyscloud_routing_skill"},
},
}
}
// DataSourceTaskManagementWorkitem registers the genesyscloud_task_management_workitem data source
func DataSourceTaskManagementWorkitem() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud task management workitem data source. Select an task management workitem by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceTaskManagementWorkitemRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Task management workitem name`,
Type: schema.TypeString,
Required: true,
},
"workbin_id": {
Description: `Id of the workbin where the desired workitem is.`,
Type: schema.TypeString,
Optional: true,
},
"worktype_id": {
Description: `Id of the worktype of the desired workitem.`,
Type: schema.TypeString,
Optional: true,
},
},
}
}
package task_management_workitem
import (
"encoding/json"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_task_management_workitem_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
// getWorkitemCreateFromResourceData maps data from schema ResourceData object to a platformclientv2.Workitemcreate
func getWorkitemCreateFromResourceData(d *schema.ResourceData) (*platformclientv2.Workitemcreate, error) {
customFields, err := buildCustomFieldsNillable(d.Get("custom_fields").(string))
if err != nil {
return nil, err
}
return &platformclientv2.Workitemcreate{
Name: platformclientv2.String(d.Get("name").(string)),
TypeId: platformclientv2.String(d.Get("worktype_id").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
DateDue: resourcedata.GetNillableTimeCustomFormat(d, "date_due", resourcedata.TimeParseFormat),
DateExpires: resourcedata.GetNillableTimeCustomFormat(d, "date_expires", resourcedata.TimeParseFormat),
DurationSeconds: resourcedata.GetNillableValue[int](d, "duration_seconds"),
Ttl: resourcedata.GetNillableValue[int](d, "ttl"),
Priority: resourcedata.GetNillableValue[int](d, "priority"),
LanguageId: resourcedata.GetNillableValue[string](d, "language_id"),
StatusId: resourcedata.GetNillableValue[string](d, "status_id"),
WorkbinId: resourcedata.GetNillableValue[string](d, "workbin_id"),
AssigneeId: resourcedata.GetNillableValue[string](d, "assignee_id"),
ExternalContactId: resourcedata.GetNillableValue[string](d, "external_contact_id"),
ExternalTag: resourcedata.GetNillableValue[string](d, "external_tag"),
QueueId: resourcedata.GetNillableValue[string](d, "queue_id"),
SkillIds: lists.BuildSdkStringListFromInterfaceArray(d, "skills_ids"),
PreferredAgentIds: lists.BuildSdkStringListFromInterfaceArray(d, "preferred_agents_ids"),
AutoStatusTransition: resourcedata.GetNillableBool(d, "auto_status_transition"),
CustomFields: customFields,
ScoredAgents: buildWorkitemScoredAgents(d.Get("scored_agents").([]interface{})),
}, nil
}
// getWorkitemUpdateFromResourceData maps data from schema ResourceData object to a platformclientv2.Workitemupdate
func getWorkitemUpdateFromResourceData(d *schema.ResourceData) (*platformclientv2.Workitemupdate, error) {
customFields, err := buildCustomFieldsNillable(d.Get("custom_fields").(string))
if err != nil {
return nil, err
}
// NOTE: The only difference from Workitemcreate is that you can't change the Worktype
return &platformclientv2.Workitemupdate{
Name: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
DateDue: resourcedata.GetNillableTimeCustomFormat(d, "date_due", resourcedata.TimeParseFormat),
DateExpires: resourcedata.GetNillableTimeCustomFormat(d, "date_expires", resourcedata.TimeParseFormat),
DurationSeconds: resourcedata.GetNillableValue[int](d, "duration_seconds"),
Ttl: resourcedata.GetNillableValue[int](d, "ttl"),
Priority: resourcedata.GetNillableValue[int](d, "priority"),
LanguageId: resourcedata.GetNillableValue[string](d, "language_id"),
StatusId: resourcedata.GetNillableValue[string](d, "status_id"),
WorkbinId: resourcedata.GetNillableValue[string](d, "workbin_id"),
AssigneeId: resourcedata.GetNillableValue[string](d, "assignee_id"),
ExternalContactId: resourcedata.GetNillableValue[string](d, "external_contact_id"),
ExternalTag: resourcedata.GetNillableValue[string](d, "external_tag"),
QueueId: resourcedata.GetNillableValue[string](d, "queue_id"),
SkillIds: lists.BuildSdkStringListFromInterfaceArray(d, "skills_ids"),
PreferredAgentIds: lists.BuildSdkStringListFromInterfaceArray(d, "preferred_agents_ids"),
AutoStatusTransition: resourcedata.GetNillableBool(d, "auto_status_transition"),
CustomFields: customFields,
ScoredAgents: buildWorkitemScoredAgents(d.Get("scored_agents").([]interface{})),
}, nil
}
// buildCustomFieldsNillable builds a Genesys Cloud *[]platformclientv2.Workitemscoredagent from a JSON string
func buildCustomFieldsNillable(fieldsJson string) (*map[string]interface{}, error) {
if fieldsJson == "" {
return nil, nil
}
fieldsInterface, err := util.JsonStringToInterface(fieldsJson)
if err != nil {
return nil, fmt.Errorf("failed to parse custom fields %s: %v", fieldsJson, err)
}
fieldsMap, ok := fieldsInterface.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("custom fields is not a JSON 'object': %v", fieldsJson)
}
return &fieldsMap, nil
}
// buildWorkitemScoredAgents maps an []interface{} into a Genesys Cloud *[]platformclientv2.Workitemscoredagent
func buildWorkitemScoredAgents(workitemScoredAgents []interface{}) *[]platformclientv2.Workitemscoredagentrequest {
workitemScoredAgentsSlice := make([]platformclientv2.Workitemscoredagentrequest, 0)
for _, workitemScoredAgent := range workitemScoredAgents {
var sdkWorkitemScoredAgent platformclientv2.Workitemscoredagentrequest
workitemScoredAgentsMap, ok := workitemScoredAgent.(map[string]interface{})
if !ok {
continue
}
sdkWorkitemScoredAgent.Id = platformclientv2.String(workitemScoredAgentsMap["agent_id"].(string))
sdkWorkitemScoredAgent.Score = platformclientv2.Int(workitemScoredAgentsMap["score"].(int))
workitemScoredAgentsSlice = append(workitemScoredAgentsSlice, sdkWorkitemScoredAgent)
}
return &workitemScoredAgentsSlice
}
// flattenRoutingSkillReferences maps a Genesys Cloud *[]platformclientv2.Routingskillreference into a []interface{}
func flattenRoutingSkillReferences(routingSkillReferences *[]platformclientv2.Routingskillreference) []interface{} {
if len(*routingSkillReferences) == 0 {
return nil
}
var skillIds []interface{}
for _, routingSkillReference := range *routingSkillReferences {
skillIds = append(skillIds, routingSkillReference.Id)
}
return skillIds
}
// flattenUserReferences maps a Genesys Cloud *[]platformclientv2.Userreference into a []interface{}
func flattenUserReferences(userReferences *[]platformclientv2.Userreference) []interface{} {
if len(*userReferences) == 0 {
return nil
}
var userIds []interface{}
for _, userReference := range *userReferences {
userIds = append(userIds, userReference.Id)
}
return userIds
}
// flattenCustomFields maps a Genesys Cloud custom fields *map[string]interface{} into a JSON string
func flattenCustomFields(customFields *map[string]interface{}) (string, error) {
if customFields == nil {
return "", nil
}
cfBytes, err := json.Marshal(customFields)
if err != nil {
return "", fmt.Errorf("error marshalling action contract %v: %v", customFields, err)
}
return string(cfBytes), nil
}
// flattenWorkitemScoredAgents maps a Genesys Cloud *[]platformclientv2.Workitemscoredagent into a []interface{}
func flattenWorkitemScoredAgents(workitemScoredAgents *[]platformclientv2.Workitemscoredagent) []interface{} {
if len(*workitemScoredAgents) == 0 {
return nil
}
var workitemScoredAgentList []interface{}
for _, workitemScoredAgent := range *workitemScoredAgents {
workitemScoredAgentMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(workitemScoredAgentMap, "agent_id", workitemScoredAgent.Agent.Id)
resourcedata.SetMapValueIfNotNil(workitemScoredAgentMap, "score", workitemScoredAgent.Score)
workitemScoredAgentList = append(workitemScoredAgentList, workitemScoredAgentMap)
}
return workitemScoredAgentList
}
package task_management_workitem_schema
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_task_management_workitem_schema.go contains the data source implementation
for the resource.
*/
// dataSourceTaskManagementWorkitemSchemaRead retrieves by name the id in question
func dataSourceTaskManagementWorkitemSchemaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementProxy(sdkConfig)
name := d.Get("name").(string)
// Query for workitem schemas by name. Retry in case new schema is not yet indexed by search.
// As schema names are non-unique, fail in case of multiple results.
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
schemas, retryable, resp, err := proxy.getTaskManagementWorkitemSchemasByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error getting workitem schema %s | error: %v", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no workitem schema found with name %s", name), resp))
}
if len(*schemas) > 1 {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("ambiguous workitem schema name: %s", name), resp))
}
schema := (*schemas)[0]
d.SetId(*schema.Id)
return nil
})
}
package task_management_workitem_schema
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_task_management_workitem_schema_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *taskManagementProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createTaskManagementWorkitemSchemaFunc func(ctx context.Context, p *taskManagementProxy, schema *platformclientv2.Dataschema) (*platformclientv2.Dataschema, *platformclientv2.APIResponse, error)
type getAllTaskManagementWorkitemSchemaFunc func(ctx context.Context, p *taskManagementProxy) (*[]platformclientv2.Dataschema, *platformclientv2.APIResponse, error)
type getTaskManagementWorkitemSchemasByNameFunc func(ctx context.Context, p *taskManagementProxy, name string) (schemas *[]platformclientv2.Dataschema, retryable bool, resp *platformclientv2.APIResponse, err error)
type getTaskManagementWorkitemSchemaByIdFunc func(ctx context.Context, p *taskManagementProxy, id string) (schema *platformclientv2.Dataschema, response *platformclientv2.APIResponse, err error)
type updateTaskManagementWorkitemSchemaFunc func(ctx context.Context, p *taskManagementProxy, id string, schema *platformclientv2.Dataschema) (*platformclientv2.Dataschema, *platformclientv2.APIResponse, error)
type deleteTaskManagementWorkitemSchemaFunc func(ctx context.Context, p *taskManagementProxy, id string) (response *platformclientv2.APIResponse, err error)
type getTaskManagementWorkitemSchemaDeletedStatusFunc func(ctx context.Context, p *taskManagementProxy, schemaId string) (isDeleted bool, resp *platformclientv2.APIResponse, err error)
// taskManagementProxy contains all of the methods that call genesys cloud APIs.
type taskManagementProxy struct {
clientConfig *platformclientv2.Configuration
taskManagementApi *platformclientv2.TaskManagementApi
createTaskManagementWorkitemSchemaAttr createTaskManagementWorkitemSchemaFunc
getAllTaskManagementWorkitemSchemaAttr getAllTaskManagementWorkitemSchemaFunc
getTaskManagementWorkitemSchemasByNameAttr getTaskManagementWorkitemSchemasByNameFunc
getTaskManagementWorkitemSchemaByIdAttr getTaskManagementWorkitemSchemaByIdFunc
updateTaskManagementWorkitemSchemaAttr updateTaskManagementWorkitemSchemaFunc
deleteTaskManagementWorkitemSchemaAttr deleteTaskManagementWorkitemSchemaFunc
getTaskManagementWorkitemSchemaDeletedStatusAttr getTaskManagementWorkitemSchemaDeletedStatusFunc
}
// newTaskManagementProxy initializes the task management proxy with all of the data needed to communicate with Genesys Cloud
func newTaskManagementProxy(clientConfig *platformclientv2.Configuration) *taskManagementProxy {
api := platformclientv2.NewTaskManagementApiWithConfig(clientConfig)
return &taskManagementProxy{
clientConfig: clientConfig,
taskManagementApi: api,
createTaskManagementWorkitemSchemaAttr: createTaskManagementWorkitemSchemaFn,
getAllTaskManagementWorkitemSchemaAttr: getAllTaskManagementWorkitemSchemaFn,
getTaskManagementWorkitemSchemasByNameAttr: getTaskManagementWorkitemSchemasByNameFn,
getTaskManagementWorkitemSchemaByIdAttr: getTaskManagementWorkitemSchemaByIdFn,
updateTaskManagementWorkitemSchemaAttr: updateTaskManagementWorkitemSchemaFn,
deleteTaskManagementWorkitemSchemaAttr: deleteTaskManagementWorkitemSchemaFn,
getTaskManagementWorkitemSchemaDeletedStatusAttr: getTaskManagementWorkitemSchemaDeletedStatusFn,
}
}
// getTaskManagementProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTaskManagementProxy(clientConfig *platformclientv2.Configuration) *taskManagementProxy {
if internalProxy == nil {
internalProxy = newTaskManagementProxy(clientConfig)
}
return internalProxy
}
// createTaskManagementWorkitemSchema creates a Genesys Cloud task management workitem schema
func (p *taskManagementProxy) createTaskManagementWorkitemSchema(ctx context.Context, schema *platformclientv2.Dataschema) (*platformclientv2.Dataschema, *platformclientv2.APIResponse, error) {
return p.createTaskManagementWorkitemSchemaAttr(ctx, p, schema)
}
// getAllTaskManagementWorkitemSchema retrieves all Genesys Cloud task management workitem schemas
func (p *taskManagementProxy) getAllTaskManagementWorkitemSchema(ctx context.Context) (*[]platformclientv2.Dataschema, *platformclientv2.APIResponse, error) {
return p.getAllTaskManagementWorkitemSchemaAttr(ctx, p)
}
// getTaskManagementWorkitemSchemaIdByName returns a single Genesys Cloud task management workitem schema by a name
func (p *taskManagementProxy) getTaskManagementWorkitemSchemasByName(ctx context.Context, name string) (schemas *[]platformclientv2.Dataschema, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorkitemSchemasByNameAttr(ctx, p, name)
}
// getTaskManagementWorkitemSchemaById returns a single Genesys Cloud task management workitem schema by Id
func (p *taskManagementProxy) getTaskManagementWorkitemSchemaById(ctx context.Context, id string) (schema *platformclientv2.Dataschema, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorkitemSchemaByIdAttr(ctx, p, id)
}
// updateTaskManagementWorkitemSchema updates a Genesys Cloud task management workitem schema
func (p *taskManagementProxy) updateTaskManagementWorkitemSchema(ctx context.Context, id string, schemaUpdate *platformclientv2.Dataschema) (*platformclientv2.Dataschema, *platformclientv2.APIResponse, error) {
return p.updateTaskManagementWorkitemSchemaAttr(ctx, p, id, schemaUpdate)
}
// deleteTaskManagementWorkitemSchema deletes a Genesys Cloud task management workitem schema by Id
func (p *taskManagementProxy) deleteTaskManagementWorkitemSchema(ctx context.Context, id string) (resp *platformclientv2.APIResponse, err error) {
return p.deleteTaskManagementWorkitemSchemaAttr(ctx, p, id)
}
// getTaskManagementWorkitemSchemaDeletedStatus gets the deleted status of a Genesys Cloud task management workitem schema
func (p *taskManagementProxy) getTaskManagementWorkitemSchemaDeletedStatus(ctx context.Context, schemaId string) (isDeleted bool, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorkitemSchemaDeletedStatusAttr(ctx, p, schemaId)
}
// createTaskManagementWorkitemSchemaFn is an implementation function for creating a Genesys Cloud task management workitem schema
func createTaskManagementWorkitemSchemaFn(ctx context.Context, p *taskManagementProxy, schema *platformclientv2.Dataschema) (*platformclientv2.Dataschema, *platformclientv2.APIResponse, error) {
log.Printf("Creating task management workitem schema: %s", *schema.Name)
createdSchema, resp, err := p.taskManagementApi.PostTaskmanagementWorkitemsSchemas(*schema)
log.Printf("Completed call to create task management workitem schema %s with status code %d, correlation id %s and err %s", *schema.Name, resp.StatusCode, resp.CorrelationID, err)
if err != nil {
return nil, resp, fmt.Errorf("failed to create task management workitem schema: %s", err)
}
return createdSchema, resp, nil
}
// getAllTaskManagementWorkitemSchemaFn is the implementation for retrieving all task management workitem schemas in Genesys Cloud
func getAllTaskManagementWorkitemSchemaFn(ctx context.Context, p *taskManagementProxy) (*[]platformclientv2.Dataschema, *platformclientv2.APIResponse, error) {
// NOTE: At the time of implementation (Preview API) retrieving schemas does not have any sort of pagination.
// It seemingly will return all schemas in one call. This might have to be updated as there may be some
// undocumented limit or if there would be changes to the API call before release.
schemas, resp, err := p.taskManagementApi.GetTaskmanagementWorkitemsSchemas()
if err != nil {
return nil, resp, fmt.Errorf("failed to get all workitem schemas: %v", err)
}
if schemas.Entities == nil || *schemas.Total == 0 {
return &([]platformclientv2.Dataschema{}), resp, nil
}
return schemas.Entities, resp, nil
}
// getTaskManagementWorkitemSchemasByNameFn is an implementation of the function to get a Genesys Cloud task management workitem schemas by name
func getTaskManagementWorkitemSchemasByNameFn(ctx context.Context, p *taskManagementProxy, name string) (matchingSchemas *[]platformclientv2.Dataschema, retryable bool, resp *platformclientv2.APIResponse, err error) {
finalSchemas := []platformclientv2.Dataschema{}
schemas, resp, err := p.getAllTaskManagementWorkitemSchema(ctx)
if err != nil {
return nil, false, resp, err
}
for _, schema := range *schemas {
if schema.Name != nil && *schema.Name == name {
finalSchemas = append(finalSchemas, schema)
}
}
if len(finalSchemas) == 0 {
return nil, true, resp, fmt.Errorf("no task management workitem schema found with name %s", name)
}
return &finalSchemas, false, resp, nil
}
// getTaskManagementWorkitemSchemaByIdFn is an implementation of the function to get a Genesys Cloud task management workitem schema by Id
func getTaskManagementWorkitemSchemaByIdFn(ctx context.Context, p *taskManagementProxy, id string) (schema *platformclientv2.Dataschema, resp *platformclientv2.APIResponse, err error) {
schema, resp, err = p.taskManagementApi.GetTaskmanagementWorkitemsSchema(id)
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve task management workitem schema by id %s: %v", id, err)
}
return schema, resp, nil
}
// updateTaskManagementWorkitemSchemaFn is an implementation of the function to update a Genesys Cloud task management workitem schema
func updateTaskManagementWorkitemSchemaFn(ctx context.Context, p *taskManagementProxy, id string, schemaUpdate *platformclientv2.Dataschema) (*platformclientv2.Dataschema, *platformclientv2.APIResponse, error) {
schema, resp, err := p.taskManagementApi.PutTaskmanagementWorkitemsSchema(id, *schemaUpdate)
if err != nil {
return nil, resp, fmt.Errorf("failed to update task management workitem schema: %s", err)
}
return schema, resp, nil
}
// deleteTaskManagementWorkitemSchemaFn is an implementation function for deleting a Genesys Cloud task management workitem schema
func deleteTaskManagementWorkitemSchemaFn(ctx context.Context, p *taskManagementProxy, id string) (resp *platformclientv2.APIResponse, err error) {
resp, err = p.taskManagementApi.DeleteTaskmanagementWorkitemsSchema(id)
if err != nil {
return resp, fmt.Errorf("failed to delete task management workitem schema: %s", err)
}
return resp, nil
}
// getTaskManagementWorkitemSchemaDeletedStatusFn is an implementation function to get the 'deleted' status of a Genesys Cloud task management workitem schema
func getTaskManagementWorkitemSchemaDeletedStatusFn(ctx context.Context, p *taskManagementProxy, schemaId string) (isDeleted bool, resp *platformclientv2.APIResponse, err error) {
apiClient := &p.clientConfig.APIClient
// create path and map variables
path := p.clientConfig.BasePath + "/api/v2/taskmanagement/workitems/schemas/" + schemaId
headerParams := make(map[string]string)
queryParams := make(map[string]string)
// oauth required
if p.clientConfig.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + p.clientConfig.AccessToken
}
// add default headers if any
for key := range p.clientConfig.DefaultHeader {
headerParams[key] = p.clientConfig.DefaultHeader[key]
}
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"
var successPayload map[string]interface{}
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil)
if err != nil {
return false, response, fmt.Errorf("failed to get workitem schema %s: %v", schemaId, err)
}
if response.Error != nil {
return false, response, fmt.Errorf("failed to get workitem schema %s: %v", schemaId, errors.New(response.ErrorMessage))
}
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
if err != nil {
return false, response, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err)
}
// Manually query for the 'deleted' property because it is removed when
// response JSON body becomes SDK Dataschema object.
if isDeleted, ok := successPayload["deleted"].(bool); ok {
return isDeleted, response, nil
}
return false, response, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err)
}
package task_management_workitem_schema
import (
"context"
"encoding/json"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_task_management_workitem_schema.go contains all of the methods that perform the core logic for a resource.
*/
// getAllTaskManagementWorkitemSchemas retrieves all of the task management workitem schemas via Terraform in the Genesys Cloud and is used for the exporter
func getAllTaskManagementWorkitemSchemas(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getTaskManagementProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
schemas, resp, err := proxy.getAllTaskManagementWorkitemSchema(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get all workitem schemas error: %s", err), resp)
}
for _, schema := range *schemas {
log.Printf("Dealing with task management workitem schema id: %s", *schema.Id)
resources[*schema.Id] = &resourceExporter.ResourceMeta{Name: *schema.Name}
}
return resources, nil
}
// createTaskManagementWorkitemSchema is used by the task_management_workitem_schema resource to create Genesys cloud task management workitem schemas
func createTaskManagementWorkitemSchema(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementProxy(sdkConfig)
dataSchema, err := BuildSdkWorkitemSchema(d, nil)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("create: failed to build task management workitem schema"), err)
}
log.Printf("Creating task management workitem schema")
schema, resp, err := proxy.createTaskManagementWorkitemSchema(ctx, dataSchema)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create task management workitem schema %s error: %s", *dataSchema.Name, err), resp)
}
d.SetId(*schema.Id)
// If enabled is set to 'false' do an update call to the schema
if enabled, ok := d.Get("enabled").(bool); ok && !enabled {
log.Printf("Updating task management workitem schema: %s, to set 'enabled' to 'false'", *schema.Name)
dataSchema.Version = platformclientv2.Int(1)
_, resp, err := proxy.updateTaskManagementWorkitemSchema(ctx, *schema.Id, dataSchema)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update task management workitem schema %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated newly created workitem schema: %s. 'enabled' set to to 'false'", *schema.Name)
}
log.Printf("Created task management workitem schema %s: %s", *schema.Name, *schema.Id)
return readTaskManagementWorkitemSchema(ctx, d, meta)
}
// readTaskManagementWorkitemSchema is used by the task_management_workitem_schema resource to read a task management workitem schema from genesys cloud
func readTaskManagementWorkitemSchema(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTaskManagementWorkitemSchema(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading task management workitem schema %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
schema, resp, getErr := proxy.getTaskManagementWorkitemSchemaById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management workitem schema %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management workitem schema %s | error: %s", d.Id(), getErr), resp))
}
schemaProps, err := json.Marshal(schema.JsonSchema.Properties)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error in reading json schema properties of %s | error: %v", *schema.Name, err), resp))
}
var schemaPropsPtr *string
if string(schemaProps) != util.NullValue {
schemaPropsStr := string(schemaProps)
schemaPropsPtr = &schemaPropsStr
}
resourcedata.SetNillableValue(d, "name", schema.Name)
resourcedata.SetNillableValue(d, "description", schema.JsonSchema.Description)
resourcedata.SetNillableValue(d, "properties", schemaPropsPtr)
resourcedata.SetNillableValue(d, "enabled", schema.Enabled)
log.Printf("Read task management workitem schema %s %s", d.Id(), *schema.Name)
return cc.CheckState(d)
})
}
// updateTaskManagementWorkitemSchema is used by the task_management_workitem_schema resource to update a task management workitem schema in Genesys Cloud
func updateTaskManagementWorkitemSchema(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementProxy(sdkConfig)
log.Printf("Getting version of workitem schema")
curSchema, resp, err := proxy.getTaskManagementWorkitemSchemaById(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get task management workitem schema By id %s error: %s", d.Id(), err), resp)
}
dataSchema, err := BuildSdkWorkitemSchema(d, curSchema.Version)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("update: failed to build task management workitem schema"), err)
}
log.Printf("Updating task management workitem schema")
updatedSchema, resp, err := proxy.updateTaskManagementWorkitemSchema(ctx, d.Id(), dataSchema)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update task management workitem schema %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated task management workitem schema %s", *updatedSchema.Id)
return readTaskManagementWorkitemSchema(ctx, d, meta)
}
// deleteTaskManagementWorkitemSchema is used by the task_management_workitem_schema resource to delete a task management workitem schema from Genesys cloud
func deleteTaskManagementWorkitemSchema(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementProxy(sdkConfig)
resp, err := proxy.deleteTaskManagementWorkitemSchema(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete task management workitem schema %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
isDeleted, resp, err := proxy.getTaskManagementWorkitemSchemaDeletedStatus(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted task management workitem schema %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting task management workitem schema %s | error: %s", d.Id(), err), resp))
}
if isDeleted {
log.Printf("Deleted task management workitem schema %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("task management workitem schema %s still exists", d.Id()), resp))
})
}
package task_management_workitem_schema
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
/*
resource_genesyscloud_task_management_workitem_schema_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the task_management_workitem_schema resource.
3. The datasource schema definitions for the task_management_workitem_schema datasource.
4. The resource exporter configuration for the task_management_workitem_schema exporter.
*/
const resourceName = "genesyscloud_task_management_workitem_schema"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceTaskManagementWorkitemSchema())
regInstance.RegisterDataSource(resourceName, DataSourceTaskManagementWorkitemSchema())
regInstance.RegisterExporter(resourceName, TaskManagementWorkitemSchemaExporter())
}
// ResourceTaskManagementWorkitemSchema registers the genesyscloud_task_management_workitem_schema resource with Terraform
func ResourceTaskManagementWorkitemSchema() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud task management workitem schema`,
CreateContext: provider.CreateWithPooledClient(createTaskManagementWorkitemSchema),
ReadContext: provider.ReadWithPooledClient(readTaskManagementWorkitemSchema),
UpdateContext: provider.UpdateWithPooledClient(updateTaskManagementWorkitemSchema),
DeleteContext: provider.DeleteWithPooledClient(deleteTaskManagementWorkitemSchema),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the Workitem Schema",
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringLenBetween(1, 50),
},
"description": {
Description: "The description of the Workitem Schema",
Optional: true,
Type: schema.TypeString,
},
"properties": {
Description: "The properties for the JSON Schema document.",
Optional: true,
Type: schema.TypeString,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"enabled": {
Description: `The schema's enabled/disabled status. A disabled schema cannot be assigned to any other entities, but the data on those entities from the schema still exists.`,
Optional: true,
Default: true,
Type: schema.TypeBool,
},
},
}
}
// TaskManagementWorkitemSchemaExporter returns the resourceExporter object used to hold the genesyscloud_task_management_workitem_schema exporter's config
func TaskManagementWorkitemSchemaExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllTaskManagementWorkitemSchemas),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{},
JsonEncodeAttributes: []string{"properties"},
}
}
// DataSourceTaskManagementWorkitemSchema registers the genesyscloud_task_management_workitem_schema data source
func DataSourceTaskManagementWorkitemSchema() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud task management workitem schema data source. Select a workitem schema by its name.`,
ReadContext: provider.ReadWithPooledClient(dataSourceTaskManagementWorkitemSchemaRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `task management workitem schema name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package task_management_workitem_schema
import (
"encoding/json"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
const (
TEXT = "text"
LONGTEXT = "longtext"
URL = "url"
IDENTIFIER = "identifier"
ENUM = "enum"
DATE = "date"
DATETIME = "datetime"
INTEGER = "integer"
NUMBER = "number"
CHECKBOX = "checkbox"
TAG = "tag"
)
type customField struct {
title string
description string
varType string
additionalProps map[string]interface{}
}
// BuildSdkWorkitemSchema takes the resource data and builds the SDK platformclientv2.Dataschema
func BuildSdkWorkitemSchema(d *schema.ResourceData, version *int) (*platformclientv2.Dataschema, error) {
// body for the creation/update of the schema
dataSchema := &platformclientv2.Dataschema{
Name: platformclientv2.String(d.Get("name").(string)),
Version: version,
JsonSchema: &platformclientv2.Jsonschemadocument{
Schema: platformclientv2.String("http://json-schema.org/draft-04/schema#"),
Title: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
},
Enabled: platformclientv2.Bool(d.Get("enabled").(bool)),
}
// Custom attributes for the schema
if d.Get("properties") != "" {
var properties map[string]interface{}
if err := json.Unmarshal([]byte(d.Get("properties").(string)), &properties); err != nil {
return nil, err
}
dataSchema.JsonSchema.Properties = &properties
}
return dataSchema, nil
}
// GenerateWorkitemSchemaResourceBasic is a public util method to generate the simplest
// schema terraform resource for testing
func GenerateWorkitemSchemaResourceBasic(resourceId, name, description string) string {
return fmt.Sprintf(`resource "%s" "%s" {
name = "%s"
description = "%s"
}
`, resourceName, resourceId, name, description)
}
func GenerateWorkitemSchemaResource(resourceId, name, description, properties, enabledStr string) string {
return fmt.Sprintf(`resource "%s" "%s" {
name = "%s"
description = "%s"
properties = %s
enabled = %s
}
`, resourceName, resourceId, name, description, properties, enabledStr)
}
package task_management_worktype
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_task_management_worktype.go contains the data source implementation
for the resource.
*/
// dataSourceTaskManagementWorktypeRead retrieves by name the id in question
func dataSourceTaskManagementWorktypeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorktypeProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
worktypeId, retryable, resp, err := proxy.getTaskManagementWorktypeIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error searching task management worktype %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no task management worktype found with name %s", name), resp))
}
d.SetId(worktypeId)
return nil
})
}
func dataSourceTaskManagementWorktypeStatusRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorktypeProxy(sdkConfig)
workTypeName := d.Get("worktype_name").(string)
statusName := d.Get("worktype_status_name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
//First retrieve the work type id by name
workType, retryable, resp, err := proxy.getTaskManagementWorktypeByName(ctx, workTypeName)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error searching task management worktype %s | error: %s", workTypeName, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no task management worktype found with name %s", workTypeName), resp))
}
for _, status := range *workType.Statuses {
if *status.Name == statusName {
d.SetId(*status.Id)
return nil
}
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No records found for management worktype %s with status name %s | error: %s", workTypeName, statusName, err), resp))
})
}
package task_management_worktype
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_task_management_worktype_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *taskManagementWorktypeProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createTaskManagementWorktypeFunc func(ctx context.Context, p *taskManagementWorktypeProxy, worktype *platformclientv2.Worktypecreate) (*platformclientv2.Worktype, *platformclientv2.APIResponse, error)
type getAllTaskManagementWorktypeFunc func(ctx context.Context, p *taskManagementWorktypeProxy) (*[]platformclientv2.Worktype, *platformclientv2.APIResponse, error)
type getTaskManagementWorktypeIdByNameFunc func(ctx context.Context, p *taskManagementWorktypeProxy, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
type getTaskManagementWorktypeByNameFunc func(ctx context.Context, p *taskManagementWorktypeProxy, name string) (workItemType *platformclientv2.Worktype, retryable bool, resp *platformclientv2.APIResponse, err error)
type getTaskManagementWorktypeByIdFunc func(ctx context.Context, p *taskManagementWorktypeProxy, id string) (worktype *platformclientv2.Worktype, response *platformclientv2.APIResponse, err error)
type updateTaskManagementWorktypeFunc func(ctx context.Context, p *taskManagementWorktypeProxy, id string, worktype *platformclientv2.Worktypeupdate) (*platformclientv2.Worktype, *platformclientv2.APIResponse, error)
type deleteTaskManagementWorktypeFunc func(ctx context.Context, p *taskManagementWorktypeProxy, id string) (response *platformclientv2.APIResponse, err error)
type createTaskManagementWorktypeStatusFunc func(ctx context.Context, p *taskManagementWorktypeProxy, worktypeId string, status *platformclientv2.Workitemstatuscreate) (*platformclientv2.Workitemstatus, *platformclientv2.APIResponse, error)
type updateTaskManagementWorktypeStatusFunc func(ctx context.Context, p *taskManagementWorktypeProxy, worktypeId string, statusId string, statusUpdate *platformclientv2.Workitemstatusupdate) (*platformclientv2.Workitemstatus, *platformclientv2.APIResponse, error)
type deleteTaskManagementWorktypeStatusFunc func(ctx context.Context, p *taskManagementWorktypeProxy, worktypeId string, statusId string) (response *platformclientv2.APIResponse, err error)
// taskManagementWorktypeProxy contains all of the methods that call genesys cloud APIs.
type taskManagementWorktypeProxy struct {
clientConfig *platformclientv2.Configuration
taskManagementApi *platformclientv2.TaskManagementApi
createTaskManagementWorktypeAttr createTaskManagementWorktypeFunc
getAllTaskManagementWorktypeAttr getAllTaskManagementWorktypeFunc
getTaskManagementWorktypeIdByNameAttr getTaskManagementWorktypeIdByNameFunc
getTaskManagementWorktypeByIdAttr getTaskManagementWorktypeByIdFunc
getTaskManagementWorktypeByNameAttr getTaskManagementWorktypeByNameFunc
updateTaskManagementWorktypeAttr updateTaskManagementWorktypeFunc
deleteTaskManagementWorktypeAttr deleteTaskManagementWorktypeFunc
createTaskManagementWorktypeStatusAttr createTaskManagementWorktypeStatusFunc
updateTaskManagementWorktypeStatusAttr updateTaskManagementWorktypeStatusFunc
deleteTaskManagementWorktypeStatusAttr deleteTaskManagementWorktypeStatusFunc
}
// newTaskManagementWorktypeProxy initializes the task management worktype proxy with all of the data needed to communicate with Genesys Cloud
func newTaskManagementWorktypeProxy(clientConfig *platformclientv2.Configuration) *taskManagementWorktypeProxy {
api := platformclientv2.NewTaskManagementApiWithConfig(clientConfig)
return &taskManagementWorktypeProxy{
clientConfig: clientConfig,
taskManagementApi: api,
createTaskManagementWorktypeAttr: createTaskManagementWorktypeFn,
getAllTaskManagementWorktypeAttr: getAllTaskManagementWorktypeFn,
getTaskManagementWorktypeIdByNameAttr: getTaskManagementWorktypeIdByNameFn,
getTaskManagementWorktypeByNameAttr: getTaskManagementWorktypeByNameFn,
getTaskManagementWorktypeByIdAttr: getTaskManagementWorktypeByIdFn,
updateTaskManagementWorktypeAttr: updateTaskManagementWorktypeFn,
deleteTaskManagementWorktypeAttr: deleteTaskManagementWorktypeFn,
createTaskManagementWorktypeStatusAttr: createTaskManagementWorktypeStatusFn,
updateTaskManagementWorktypeStatusAttr: updateTaskManagementWorktypeStatusFn,
deleteTaskManagementWorktypeStatusAttr: deleteTaskManagementWorktypeStatusFn,
}
}
// getTaskManagementWorktypeProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTaskManagementWorktypeProxy(clientConfig *platformclientv2.Configuration) *taskManagementWorktypeProxy {
if internalProxy == nil {
internalProxy = newTaskManagementWorktypeProxy(clientConfig)
}
return internalProxy
}
// createTaskManagementWorktype creates a Genesys Cloud task management worktype
func (p *taskManagementWorktypeProxy) createTaskManagementWorktype(ctx context.Context, taskManagementWorktype *platformclientv2.Worktypecreate) (*platformclientv2.Worktype, *platformclientv2.APIResponse, error) {
return p.createTaskManagementWorktypeAttr(ctx, p, taskManagementWorktype)
}
// getTaskManagementWorktype retrieves all Genesys Cloud task management worktype
func (p *taskManagementWorktypeProxy) getAllTaskManagementWorktype(ctx context.Context) (*[]platformclientv2.Worktype, *platformclientv2.APIResponse, error) {
return p.getAllTaskManagementWorktypeAttr(ctx, p)
}
// getTaskManagementWorktypeIdByName returns a single Genesys Cloud task management worktype by a name
func (p *taskManagementWorktypeProxy) getTaskManagementWorktypeIdByName(ctx context.Context, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorktypeIdByNameAttr(ctx, p, name)
}
// getTaskManagementWorktypeByName returns a single Genesys Cloud task management worktype by a name
func (p *taskManagementWorktypeProxy) getTaskManagementWorktypeByName(ctx context.Context, name string) (workItemType *platformclientv2.Worktype, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorktypeByNameAttr(ctx, p, name)
}
// getTaskManagementWorktypeById returns a single Genesys Cloud task management worktype by Id
func (p *taskManagementWorktypeProxy) getTaskManagementWorktypeById(ctx context.Context, id string) (taskManagementWorktype *platformclientv2.Worktype, resp *platformclientv2.APIResponse, err error) {
return p.getTaskManagementWorktypeByIdAttr(ctx, p, id)
}
// updateTaskManagementWorktype updates a Genesys Cloud task management worktype
func (p *taskManagementWorktypeProxy) updateTaskManagementWorktype(ctx context.Context, id string, taskManagementWorktype *platformclientv2.Worktypeupdate) (*platformclientv2.Worktype, *platformclientv2.APIResponse, error) {
return p.updateTaskManagementWorktypeAttr(ctx, p, id, taskManagementWorktype)
}
// deleteTaskManagementWorktype deletes a Genesys Cloud task management worktype by Id
func (p *taskManagementWorktypeProxy) deleteTaskManagementWorktype(ctx context.Context, id string) (resp *platformclientv2.APIResponse, err error) {
return p.deleteTaskManagementWorktypeAttr(ctx, p, id)
}
// createTaskManagementWorktypeStatus creates a Genesys Cloud task management worktype status
func (p *taskManagementWorktypeProxy) createTaskManagementWorktypeStatus(ctx context.Context, worktypeId string, status *platformclientv2.Workitemstatuscreate) (*platformclientv2.Workitemstatus, *platformclientv2.APIResponse, error) {
return p.createTaskManagementWorktypeStatusAttr(ctx, p, worktypeId, status)
}
// updateTaskManagementWorktypeStatus updates a Genesys Cloud task management worktype status
func (p *taskManagementWorktypeProxy) updateTaskManagementWorktypeStatus(ctx context.Context, worktypeId string, statusId string, statusUpdate *platformclientv2.Workitemstatusupdate) (*platformclientv2.Workitemstatus, *platformclientv2.APIResponse, error) {
return p.updateTaskManagementWorktypeStatusAttr(ctx, p, worktypeId, statusId, statusUpdate)
}
// deleteTaskManagementWorktypeStatus deletes a Genesys Cloud task management worktype status by Id
func (p *taskManagementWorktypeProxy) deleteTaskManagementWorktypeStatus(ctx context.Context, worktypeId string, statusId string) (response *platformclientv2.APIResponse, err error) {
return p.deleteTaskManagementWorktypeStatusAttr(ctx, p, worktypeId, statusId)
}
// createTaskManagementWorktypeFn is an implementation function for creating a Genesys Cloud task management worktype
func createTaskManagementWorktypeFn(ctx context.Context, p *taskManagementWorktypeProxy, taskManagementWorktype *platformclientv2.Worktypecreate) (*platformclientv2.Worktype, *platformclientv2.APIResponse, error) {
log.Printf("Creating task management worktype: %s", *taskManagementWorktype.Name)
worktype, resp, err := p.taskManagementApi.PostTaskmanagementWorktypes(*taskManagementWorktype)
log.Printf("Completed call to create task management worktype %s with status code %d, correlation id %s and err %s", *taskManagementWorktype.Name, resp.StatusCode, resp.CorrelationID, err)
if err != nil {
return nil, resp, fmt.Errorf("failed to create task management worktype: %s", err)
}
return worktype, resp, nil
}
// getAllTaskManagementWorktypeFn is the implementation for retrieving all task management worktype in Genesys Cloud
func getAllTaskManagementWorktypeFn(ctx context.Context, p *taskManagementWorktypeProxy) (*[]platformclientv2.Worktype, *platformclientv2.APIResponse, error) {
var allWorktypes []platformclientv2.Worktype
pageSize := 200
after := ""
var response *platformclientv2.APIResponse
for {
queryReq := &platformclientv2.Worktypequeryrequest{
PageSize: &pageSize,
After: &after,
}
worktypes, resp, err := p.taskManagementApi.PostTaskmanagementWorktypesQuery(*queryReq)
response = resp
if err != nil {
return nil, resp, fmt.Errorf("failed to get worktypes: %v", err)
}
allWorktypes = append(allWorktypes, *worktypes.Entities...)
// Exit loop if there are no more 'pages'
if worktypes.After == nil || *worktypes.After == "" {
break
}
after = *worktypes.After
}
return &allWorktypes, response, nil
}
// Looksup a worktype by name
func getWorkType(name string, p *taskManagementWorktypeProxy) (*platformclientv2.Worktype, bool, *platformclientv2.APIResponse, error) {
pageSize := 100
filterType := "String"
filterOperator := "EQ"
filterNameKey := "name"
filterNameValues := []string{name}
queryReq := &platformclientv2.Worktypequeryrequest{
PageSize: &pageSize,
Filters: &[]platformclientv2.Workitemfilter{
// Filter for the worktype name
platformclientv2.Workitemfilter{
Name: &filterNameKey,
VarType: &filterType,
Operator: &filterOperator,
Values: &filterNameValues,
},
},
}
worktypes, resp, err := p.taskManagementApi.PostTaskmanagementWorktypesQuery(*queryReq)
if err != nil {
return nil, false, resp, fmt.Errorf("failed to get worktype %s: %v", name, err)
}
if worktypes.Entities == nil || len(*worktypes.Entities) == 0 {
return nil, true, resp, fmt.Errorf("no task management worktype found with name %s", name)
}
if len(*worktypes.Entities) > 1 {
return nil, true, resp, fmt.Errorf("%d workitem types have been found with the same name: %s . Unable to resolve to a single id", len(*worktypes.Entities), name)
}
workType := (*worktypes.Entities)[0]
return &workType, false, resp, nil
}
// getTaskManagementWorktypeIdByNameFn is an implementation of the function to get a Genesys Cloud task management worktype by name
func getTaskManagementWorktypeIdByNameFn(ctx context.Context, p *taskManagementWorktypeProxy, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
worktype, retry, resp, err := getWorkType(name, p)
if err != nil {
return "", retry, resp, err
}
log.Printf("Retrieved the task management worktype id %s by name %s", *worktype.Id, name)
return *worktype.Id, false, resp, nil
}
// getTaskManagementWorktypeByNameFn Retrieves the full worktype item rather than just the id
func getTaskManagementWorktypeByNameFn(ctx context.Context, p *taskManagementWorktypeProxy, name string) (workItemType *platformclientv2.Worktype, retryable bool, resp *platformclientv2.APIResponse, err error) {
worktype, retry, resp, err := getWorkType(name, p)
if err != nil {
return nil, retry, resp, err
}
log.Printf("Retrieved the task management worktype %s by name %s", *worktype.Id, name)
return worktype, false, resp, nil
}
// getTaskManagementWorktypeByIdFn is an implementation of the function to get a Genesys Cloud task management worktype by Id
func getTaskManagementWorktypeByIdFn(ctx context.Context, p *taskManagementWorktypeProxy, id string) (taskManagementWorktype *platformclientv2.Worktype, resp *platformclientv2.APIResponse, err error) {
worktype, resp, err := p.taskManagementApi.GetTaskmanagementWorktype(id, []string{})
if err != nil {
return nil, resp, fmt.Errorf("failed to retrieve task management worktype by id %s: %s", id, err)
}
return worktype, resp, nil
}
// updateTaskManagementWorktypeFn is an implementation of the function to update a Genesys Cloud task management worktype
func updateTaskManagementWorktypeFn(ctx context.Context, p *taskManagementWorktypeProxy, id string, worktypeUpdate *platformclientv2.Worktypeupdate) (*platformclientv2.Worktype, *platformclientv2.APIResponse, error) {
worktype, resp, err := p.taskManagementApi.PatchTaskmanagementWorktype(id, *worktypeUpdate)
if err != nil {
return nil, resp, fmt.Errorf("failed to update task management worktype %s: %v", id, err)
}
return worktype, resp, nil
}
// deleteTaskManagementWorktypeFn is an implementation function for deleting a Genesys Cloud task management worktype
func deleteTaskManagementWorktypeFn(ctx context.Context, p *taskManagementWorktypeProxy, id string) (resp *platformclientv2.APIResponse, err error) {
resp, err = p.taskManagementApi.DeleteTaskmanagementWorktype(id)
if err != nil {
return resp, fmt.Errorf("failed to delete task management worktype %s: %v", id, err)
}
return resp, nil
}
// createTaskManagementWorktypeStatusFn is an implementation function for creating a Genesys Cloud task management worktype status
func createTaskManagementWorktypeStatusFn(ctx context.Context, p *taskManagementWorktypeProxy, worktypeId string, status *platformclientv2.Workitemstatuscreate) (*platformclientv2.Workitemstatus, *platformclientv2.APIResponse, error) {
wtStatus, resp, err := p.taskManagementApi.PostTaskmanagementWorktypeStatuses(worktypeId, *status)
if err != nil {
return nil, resp, fmt.Errorf("failed to create task management worktype status: %s. For worktype: %s", err, worktypeId)
}
return wtStatus, resp, nil
}
// updateTaskManagementWorktypeStatusFn is an implementation of the function to update a Genesys Cloud task management worktype status
func updateTaskManagementWorktypeStatusFn(ctx context.Context, p *taskManagementWorktypeProxy, worktypeId string, statusId string, statusUpdate *platformclientv2.Workitemstatusupdate) (*platformclientv2.Workitemstatus, *platformclientv2.APIResponse, error) {
wtStatus, resp, err := p.taskManagementApi.PatchTaskmanagementWorktypeStatus(worktypeId, statusId, *statusUpdate)
if err != nil {
return nil, resp, fmt.Errorf("failed to update task management worktype status: %s. For worktype: %s", err, worktypeId)
}
return wtStatus, resp, nil
}
// deleteTaskManagementWorktypeStatusFn is an implementation function for deleting a Genesys Cloud task management worktype status
func deleteTaskManagementWorktypeStatusFn(ctx context.Context, p *taskManagementWorktypeProxy, worktypeId string, statusId string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.taskManagementApi.DeleteTaskmanagementWorktypeStatus(worktypeId, statusId)
if err != nil {
return resp, fmt.Errorf("failed to delete task management worktype status %s: %v", statusId, err)
}
return resp, nil
}
package task_management_worktype
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_task_management_worktype.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthTaskManagementWorktype retrieves all of the task management worktype via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthTaskManagementWorktypes(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getTaskManagementWorktypeProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
worktypes, resp, err := proxy.getAllTaskManagementWorktype(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get task management worktype error: %s", err), resp)
}
for _, worktype := range *worktypes {
resources[*worktype.Id] = &resourceExporter.ResourceMeta{Name: *worktype.Name}
}
return resources, nil
}
// createTaskManagementWorktype is used by the task_management_worktype resource to create Genesys cloud task management worktype
func createTaskManagementWorktype(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorktypeProxy(sdkConfig)
taskManagementWorktype := getWorktypecreateFromResourceData(d)
// Create the base worktype
log.Printf("Creating task management worktype %s", *taskManagementWorktype.Name)
worktype, resp, err := proxy.createTaskManagementWorktype(ctx, &taskManagementWorktype)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create task management worktype %s error: %s", *taskManagementWorktype.Name, err), resp)
}
log.Printf("Created the base task management worktype %s", *worktype.Id)
d.SetId(*worktype.Id)
// Create and update (for referencing other status) the worktype statuses
log.Printf("Creating the task management worktype statuses of %s", *worktype.Id)
statuses := d.Get("statuses").(*schema.Set).List()
if _, err := createWorktypeStatuses(ctx, proxy, *worktype.Id, statuses); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to create task management worktype statuses"), err)
}
log.Printf("Updating the destination statuses of the statuses of worktype %s", *worktype.Id)
if _, err := updateWorktypeStatuses(ctx, proxy, *worktype.Id, statuses, true); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to update task management worktype statuses"), err)
}
// Update the worktype if 'default_status_name' is set
if d.HasChange("default_status_name") {
time.Sleep(5 * time.Second)
err := updateDefaultStatusName(ctx, proxy, d, *worktype.Id)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to update default status name of worktype"), err)
}
}
log.Printf("Created the task management worktype statuses of %s", *worktype.Id)
return readTaskManagementWorktype(ctx, d, meta)
}
// readTaskManagementWorktype is used by the task_management_worktype resource to read a task management worktype from genesys cloud
func readTaskManagementWorktype(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorktypeProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTaskManagementWorktype(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading task management worktype %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
worktype, resp, getErr := proxy.getTaskManagementWorktypeById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management worktype %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read task management worktype %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", worktype.Name)
resourcedata.SetNillableValue(d, "description", worktype.Description)
resourcedata.SetNillableReferenceDivision(d, "division_id", worktype.Division)
if worktype.DefaultWorkbin != nil {
resourcedata.SetNillableValue(d, "default_workbin_id", worktype.DefaultWorkbin.Id)
}
// Default status can be an empty object
if worktype.DefaultStatus != nil && worktype.DefaultStatus.Id != nil {
if statusName := getStatusNameFromId(*worktype.DefaultStatus.Id, worktype.Statuses); statusName != nil {
d.Set("default_status_name", statusName)
}
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "statuses", worktype.Statuses, flattenWorkitemStatuses)
resourcedata.SetNillableValue(d, "default_duration_seconds", worktype.DefaultDurationSeconds)
resourcedata.SetNillableValue(d, "default_expiration_seconds", worktype.DefaultExpirationSeconds)
resourcedata.SetNillableValue(d, "default_due_duration_seconds", worktype.DefaultDueDurationSeconds)
resourcedata.SetNillableValue(d, "default_priority", worktype.DefaultPriority)
resourcedata.SetNillableValue(d, "default_ttl_seconds", worktype.DefaultTtlSeconds)
if worktype.DefaultLanguage != nil {
resourcedata.SetNillableValue(d, "default_language_id", worktype.DefaultLanguage.Id)
}
if worktype.DefaultQueue != nil {
resourcedata.SetNillableValue(d, "default_queue_id", worktype.DefaultQueue.Id)
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "default_skills_ids", worktype.DefaultSkills, flattenRoutingSkillReferences)
resourcedata.SetNillableValue(d, "assignment_enabled", worktype.AssignmentEnabled)
if worktype.Schema != nil {
resourcedata.SetNillableValue(d, "schema_id", worktype.Schema.Id)
resourcedata.SetNillableValue(d, "schema_version", worktype.Schema.Version)
}
log.Printf("Read task management worktype %s %s", d.Id(), *worktype.Name)
return cc.CheckState(d)
})
}
// updateTaskManagementWorktype is used by the task_management_worktype resource to update a task management worktype in Genesys Cloud
func updateTaskManagementWorktype(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorktypeProxy(sdkConfig)
// Update the base configuration of the Worktype
taskManagementWorktype := getWorktypeupdateFromResourceData(d, nil)
if d.HasChangesExcept("statuses", "default_status_name") {
worktype, resp, err := proxy.updateTaskManagementWorktype(ctx, d.Id(), &taskManagementWorktype)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update task management worktype %s error: %s", *taskManagementWorktype.Name, err), resp)
}
log.Printf("Updated base configuration of task management worktype %s", *worktype.Id)
}
// Get the current state of the worktype because we will cross-check if any of the existing ones
// need to be deleted
oldWorktype, resp, err := proxy.getTaskManagementWorktypeById(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get task management worktype %s error: %s", d.Id(), err), resp)
}
oldStatusIds := []string{}
for _, oldStatus := range *oldWorktype.Statuses {
oldStatusIds = append(oldStatusIds, *oldStatus.Id)
}
// We'll use this to keep track of the actual status ids as a result of the worktype update.
// Any ids not here will be deleted eventually as the last step.
statusIdsToStay := []string{}
statuses := d.Get("statuses").(*schema.Set).List()
// If the status in the state still has an id that means there is no update to it and so should not be included
// in the API update (GC API gives error if update is called but actually no diff).
forUpdateOrCreation := make([]interface{}, 0)
for _, status := range statuses {
statusMap := status.(map[string]interface{})
if statusMap["id"] == "" {
forUpdateOrCreation = append(forUpdateOrCreation, status)
} else {
statusIdsToStay = append(statusIdsToStay, statusMap["id"].(string))
}
}
forCreation, forUpdate := getStatusesForUpdateAndCreation(forUpdateOrCreation, oldWorktype.Statuses)
// Create new statuses
log.Printf("Creating the task management worktype statuses of %s", d.Id())
if _, err := createWorktypeStatuses(ctx, proxy, d.Id(), forCreation); err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to create task management worktype statuses"), err)
}
// Update the newly created statuses with status refs
log.Printf("Updating the newly created statuses of worktype %s", d.Id())
createdStatuses, err := updateWorktypeStatuses(ctx, proxy, d.Id(), forCreation, true)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to update task management worktype statuses"), err)
}
for _, updateStat := range *createdStatuses {
statusIdsToStay = append(statusIdsToStay, *updateStat.Id)
}
// Update the other already existing statuses for reference or other property updates
log.Printf("Updating the destination statuses of the statuses of worktype %s", d.Id())
updatedStatuses, err := updateWorktypeStatuses(ctx, proxy, d.Id(), forUpdate, false)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to update task management worktype statuses"), err)
}
for _, updateStat := range *updatedStatuses {
statusIdsToStay = append(statusIdsToStay, *updateStat.Id)
}
// Delete statuses that are no longer defined in the configuration
log.Printf("Cleaning up statuses of worktype %s", d.Id())
forDeletionIds := lists.SliceDifference(oldStatusIds, statusIdsToStay)
// Go through and clear the status references first to avoid dependency errors on deletion
log.Printf("Clearing references of statuses for deletion of worktype %s", d.Id())
for _, forDeletionId := range forDeletionIds {
updateForCleaning := platformclientv2.Workitemstatusupdate{}
// // Force these properties as 'null' for the API request
updateForCleaning.SetField("DestinationStatusIds", &[]string{})
updateForCleaning.SetField("DefaultDestinationStatusId", nil)
updateForCleaning.SetField("StatusTransitionDelaySeconds", nil)
updateForCleaning.SetField("StatusTransitionTime", nil)
// We put a random description so we can ensure there is a 'change' in the status.
// Else we'll get a 400 error if the status has no destination status /default status to begin with
// This is simpler than checking the status fields if there are any changes.
// Since this status is for deletion anyway we shouldn't care about this managed update.
description := "this status is set for deletion by CX as Code " + uuid.NewString()
updateForCleaning.SetField("Description", &description)
if _, resp, err := proxy.updateTaskManagementWorktypeStatus(ctx, d.Id(), forDeletionId, &updateForCleaning); err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to clean up references of task management worktype status %s error: %s", d.Id(), err), resp)
}
}
// Actually delete the status
log.Printf("Deleting unused statuses of worktype %s", d.Id())
for _, forDeletionId := range forDeletionIds {
if resp, err := proxy.deleteTaskManagementWorktypeStatus(ctx, d.Id(), forDeletionId); err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete task management worktype status %s error: %s", forDeletionId, err), resp)
}
}
// Update the worktype if 'default_status_name' is changed
// We do this last so that the statuses are surely updated first
if d.HasChange("default_status_name") {
log.Printf("Updating default status of worktype %s", d.Id())
time.Sleep(5 * time.Second)
err := updateDefaultStatusName(ctx, proxy, d, d.Id())
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to update default status name of worktype"), err)
}
}
log.Printf("Finished updating worktype %s", d.Id())
return readTaskManagementWorktype(ctx, d, meta)
}
// deleteTaskManagementWorktype is used by the task_management_worktype resource to delete a task management worktype from Genesys cloud
func deleteTaskManagementWorktype(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTaskManagementWorktypeProxy(sdkConfig)
resp, err := proxy.deleteTaskManagementWorktype(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete task management worktype %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getTaskManagementWorktypeById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted task management worktype %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting task management worktype %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("task management worktype %s still exists", d.Id()), resp))
})
}
package task_management_worktype
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
/*
resource_genesycloud_task_management_worktype_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the task_management_worktype resource.
3. The datasource schema definitions for the task_management_worktype datasource.
4. The resource exporter configuration for the task_management_worktype exporter.
*/
const resourceName = "genesyscloud_task_management_worktype"
const worktypeStatusDataSourceName = "genesyscloud_task_management_worktype_status"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceTaskManagementWorktype())
regInstance.RegisterDataSource(resourceName, DataSourceTaskManagementWorktype())
regInstance.RegisterDataSource(worktypeStatusDataSourceName, DataSourceTaskManagementWorktypeStatus())
regInstance.RegisterExporter(resourceName, TaskManagementWorktypeExporter())
}
// ResourceTaskManagementWorktype registers the genesyscloud_task_management_worktype resource with Terraform
func ResourceTaskManagementWorktype() *schema.Resource {
workitemStatusResource := &schema.Resource{
Schema: map[string]*schema.Schema{
`id`: {
Description: `Read-only identifier of the workitem status`,
Computed: true,
Type: schema.TypeString,
},
`name`: {
Description: `Name of the status`,
Required: true,
Type: schema.TypeString,
},
`description`: {
Description: `The description of the Status.`,
Optional: true,
Type: schema.TypeString,
},
`category`: {
Description: `The Category of the Status.`,
Required: true,
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"Open", "InProgress", "Waiting", "Closed"}, false),
},
`destination_status_names`: {
Description: `The names of the Statuses the Status can transition to. If null, the status can transition to any other status.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
`default_destination_status_name`: {
Description: `Name of the default destination status to which this Status will transition to if auto status transition enabled.`,
Optional: true,
Type: schema.TypeString,
},
`status_transition_delay_seconds`: {
Description: `Delay in seconds for auto status transition`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
ValidateFunc: validation.IntAtLeast(60),
},
`status_transition_time`: {
Description: `Time (HH:MM:SS format) at which auto status transition will occur after statusTransitionDelaySeconds delay. To set Time, the statusTransitionDelaySeconds must be equal to or greater than 86400 i.e. a day`,
Optional: true,
Computed: true,
Type: schema.TypeString,
ValidateDiagFunc: gcloud.ValidateTime,
},
},
}
return &schema.Resource{
Description: `Genesys Cloud task management worktype`,
CreateContext: provider.CreateWithPooledClient(createTaskManagementWorktype),
ReadContext: provider.ReadWithPooledClient(readTaskManagementWorktype),
UpdateContext: provider.UpdateWithPooledClient(updateTaskManagementWorktype),
DeleteContext: provider.DeleteWithPooledClient(deleteTaskManagementWorktype),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
`name`: {
Description: `The name of the Worktype.`,
Required: true,
Type: schema.TypeString,
},
`description`: {
Description: `The description of the Worktype.`,
Optional: true,
Type: schema.TypeString,
},
`division_id`: {
Description: `The division to which this entity belongs.`,
Optional: true,
Computed: true,
Type: schema.TypeString,
},
`statuses`: {
Description: `The list of possible statuses for Workitems created from the Worktype.`,
Optional: true,
Type: schema.TypeSet,
Elem: workitemStatusResource,
},
`default_status_name`: {
Description: `The name of the default status for Workitems created from the Worktype. This status should be defined in 'statuses'.`,
Optional: true,
Type: schema.TypeString,
},
`default_workbin_id`: {
Description: `The default Workbin for Workitems created from the Worktype.`,
Required: true,
Type: schema.TypeString,
},
`default_duration_seconds`: {
Description: `The default duration in seconds for Workitems created from the Worktype.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`default_expiration_seconds`: {
Description: `The default expiration time in seconds for Workitems created from the Worktype.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`default_due_duration_seconds`: {
Description: `The default due duration in seconds for Workitems created from the Worktype.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`default_priority`: {
Description: `The default priority for Workitems created from the Worktype. The valid range is between -25,000,000 and 25,000,000.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
ValidateFunc: validation.IntBetween(-25000000, 25000000),
},
`default_ttl_seconds`: {
Description: `The default time to time to live in seconds for Workitems created from the Worktype.`,
Optional: true,
Computed: true,
Type: schema.TypeInt,
},
`default_language_id`: {
Description: `The default routing language for Workitems created from the Worktype.`,
Optional: true,
Type: schema.TypeString,
},
`default_queue_id`: {
Description: `The default queue for Workitems created from the Worktype.`,
Optional: true,
Type: schema.TypeString,
},
`default_skills_ids`: {
Description: `The default skills for Workitems created from the Worktype.`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
MaxItems: 20,
},
`assignment_enabled`: {
Description: `When set to true, Workitems will be sent to the queue of the Worktype as they are created. Default value is false.`,
Optional: true,
Type: schema.TypeBool,
},
`schema_id`: {
Description: `Id of the workitem schema.`,
Required: true,
Type: schema.TypeString,
},
`schema_version`: {
Description: `Version of the workitem schema to use. If not provided, the worktype will use the latest version.`,
Optional: true,
Type: schema.TypeInt,
},
},
}
}
// TaskManagementWorktypeExporter returns the resourceExporter object used to hold the genesyscloud_task_management_worktype exporter's config
func TaskManagementWorktypeExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthTaskManagementWorktypes),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
"default_workbin_id": {RefType: "genesyscloud_task_management_workbin"},
"default_language_id": {RefType: "genesyscloud_routing_language"},
"default_queue_id": {RefType: "genesyscloud_routing_queue"},
"default_skills_ids": {RefType: "genesyscloud_routing_skill"},
"schema_id": {RefType: "genesyscloud_task_management_workitem_schema"},
},
ExcludedAttributes: []string{"statuses.id"},
}
}
// DataSourceTaskManagementWorktype registers the genesyscloud_task_management_worktype data source
func DataSourceTaskManagementWorktype() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud task management worktype data source. Select a task management worktype by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceTaskManagementWorktypeRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `Task management worktype name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
// DataSourceTaskManagementWorktypeStatus registers the genesyscloud_task_management_worktype_status data source
func DataSourceTaskManagementWorktypeStatus() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud task management worktype_status data source. Select a status by worktype name and status name`,
ReadContext: provider.ReadWithPooledClient(dataSourceTaskManagementWorktypeStatusRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"worktype_name": {
Description: `Task management worktype name`,
Type: schema.TypeString,
Required: true,
},
"worktype_status_name": {
Description: `Task management worktype status name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package task_management_worktype
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The resource_genesyscloud_task_management_worktype_utils.go file contains various helper methods to marshal
and unmarshal data into formats consumable by Terraform and/or Genesys Cloud.
*/
type worktypeConfig struct {
resID string
name string
description string
divisionId string
statuses []worktypeStatusConfig
defaultStatusName string
defaultWorkbinId string
defaultDurationS int
defaultExpirationS int
defaultDueDurationS int
defaultPriority int
defaultTtlS int
defaultLanguageId string
defaultQueueId string
defaultSkillIds []string
assignmentEnabled bool
schemaId string
schemaVersion int
}
type worktypeStatusConfig struct {
id string
name string
description string
category string
destinationStatusNames []string
defaultDestinationStatusName string
transitionDelay int
statusTransitionTime string
}
// getWorktypeCreateFromResourceData maps data from schema ResourceData object to a platformclientv2.Worktypecreate
func getWorktypecreateFromResourceData(d *schema.ResourceData) platformclientv2.Worktypecreate {
worktype := platformclientv2.Worktypecreate{
Name: platformclientv2.String(d.Get("name").(string)),
DivisionId: platformclientv2.String(d.Get("division_id").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
DisableDefaultStatusCreation: platformclientv2.Bool(true),
DefaultWorkbinId: platformclientv2.String(d.Get("default_workbin_id").(string)),
SchemaId: platformclientv2.String(d.Get("schema_id").(string)),
SchemaVersion: resourcedata.GetNillableValue[int](d, "schema_version"),
DefaultPriority: platformclientv2.Int(d.Get("default_priority").(int)),
DefaultLanguageId: resourcedata.GetNillableValue[string](d, "default_language_id"),
DefaultQueueId: resourcedata.GetNillableValue[string](d, "default_queue_id"),
DefaultSkillIds: lists.BuildSdkStringListFromInterfaceArray(d, "default_skills_ids"),
AssignmentEnabled: platformclientv2.Bool(d.Get("assignment_enabled").(bool)),
DefaultDurationSeconds: resourcedata.GetNillableValue[int](d, "default_duration_seconds"),
DefaultExpirationSeconds: resourcedata.GetNillableValue[int](d, "default_expiration_seconds"),
DefaultDueDurationSeconds: resourcedata.GetNillableValue[int](d, "default_due_duration_seconds"),
DefaultTtlSeconds: resourcedata.GetNillableValue[int](d, "default_ttl_seconds"),
}
return worktype
}
// getWorktypeupdateFromResourceData maps data from schema ResourceData object to a platformclientv2.Worktypeupdate
func getWorktypeupdateFromResourceData(d *schema.ResourceData, statuses *[]platformclientv2.Workitemstatus) platformclientv2.Worktypeupdate {
worktype := platformclientv2.Worktypeupdate{
Name: platformclientv2.String(d.Get("name").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
DefaultWorkbinId: platformclientv2.String(d.Get("default_workbin_id").(string)),
DefaultPriority: platformclientv2.Int(d.Get("default_priority").(int)),
DefaultLanguageId: resourcedata.GetNillableValue[string](d, "default_language_id"),
DefaultQueueId: resourcedata.GetNillableValue[string](d, "default_queue_id"),
DefaultSkillIds: lists.BuildSdkStringListFromInterfaceArray(d, "default_skills_ids"),
AssignmentEnabled: platformclientv2.Bool(d.Get("assignment_enabled").(bool)),
DefaultDurationSeconds: resourcedata.GetNillableValue[int](d, "default_duration_seconds"),
DefaultExpirationSeconds: resourcedata.GetNillableValue[int](d, "default_expiration_seconds"),
DefaultDueDurationSeconds: resourcedata.GetNillableValue[int](d, "default_due_duration_seconds"),
DefaultTtlSeconds: resourcedata.GetNillableValue[int](d, "default_ttl_seconds"),
DefaultStatusId: getStatusIdFromName(d.Get("default_status_name").(string), statuses),
SchemaVersion: resourcedata.GetNillableValue[int](d, "schema_version"),
}
return worktype
}
// getStatusFromName gets a platformclientv2.Workitemstatus from a *[]platformclientv2.Workitemstatu by name
func getStatusFromName(statusName string, statuses *[]platformclientv2.Workitemstatus) *platformclientv2.Workitemstatus {
if statuses == nil {
return nil
}
for _, apiStatus := range *statuses {
if statusName == *apiStatus.Name {
return &apiStatus
}
}
return nil
}
// getStatusIdFromName gets a status id from a *[]platformclientv2.Workitemstatu by status name
func getStatusIdFromName(statusName string, statuses *[]platformclientv2.Workitemstatus) *string {
if statuses == nil {
return nil
}
for _, apiStatus := range *statuses {
if statusName == *apiStatus.Name {
return apiStatus.Id
}
}
return nil
}
// getStatusIdFromName gets the status name from a *[]platformclientv2.Workitemstatus by status id
func getStatusNameFromId(statusId string, statuses *[]platformclientv2.Workitemstatus) *string {
if statuses == nil {
return nil
}
for _, apiStatus := range *statuses {
if statusId == *apiStatus.Id {
return apiStatus.Name
}
}
return nil
}
// buildWorkitemStatusCreates maps an []interface{} into a Genesys Cloud *[]platformclientv2.Workitemstatuscreate
func buildWorkitemStatusCreates(workitemStatuses []interface{}) *[]platformclientv2.Workitemstatuscreate {
workitemStatusesSlice := make([]platformclientv2.Workitemstatuscreate, 0)
for _, workitemStatus := range workitemStatuses {
var sdkWorkitemStatus platformclientv2.Workitemstatuscreate
workitemStatusMap, ok := workitemStatus.(map[string]interface{})
if !ok {
continue
}
resourcedata.BuildSDKStringValueIfNotNil(&sdkWorkitemStatus.Name, workitemStatusMap, "name")
resourcedata.BuildSDKStringValueIfNotNil(&sdkWorkitemStatus.Category, workitemStatusMap, "category")
resourcedata.BuildSDKStringValueIfNotNil(&sdkWorkitemStatus.Description, workitemStatusMap, "description")
resourcedata.BuildSDKStringValueIfNotNil(&sdkWorkitemStatus.StatusTransitionTime, workitemStatusMap, "status_transition_time")
if statusTransitionDelaySec, ok := workitemStatusMap["status_transition_delay_seconds"]; ok && statusTransitionDelaySec.(int) > 0 {
sdkWorkitemStatus.StatusTransitionDelaySeconds = platformclientv2.Int(statusTransitionDelaySec.(int))
}
workitemStatusesSlice = append(workitemStatusesSlice, sdkWorkitemStatus)
}
return &workitemStatusesSlice
}
// buildWorkitemStatusUpdates maps an []interface{} into a Genesys Cloud *[]platformclientv2.Workitemstatusupdate
// workitemStatuses is the terraform resource object attribute while apiStatuses is the existing Genesys Cloud
// statuses to be used as reference for the 'destination status ids'
func buildWorkitemStatusUpdates(workitemStatuses []interface{}, apiStatuses *[]platformclientv2.Workitemstatus) *[]platformclientv2.Workitemstatusupdate {
workitemStatussSlice := make([]platformclientv2.Workitemstatusupdate, 0)
// Inner func get the status id from a status name.
getStatusIdFromNameFn := func(statusName string) *string {
return getStatusIdFromName(statusName, apiStatuses)
}
// Inner func for use in building the destination status ids from the status names defined
buildStatusIdFn := func(statusNames []interface{}) *[]string {
statusIds := []string{}
for _, name := range statusNames {
if statusId := getStatusIdFromName(name.(string), apiStatuses); statusId != nil {
statusIds = append(statusIds, *statusId)
}
}
return &statusIds
}
for _, workitemStatus := range workitemStatuses {
sdkWorkitemStatus := platformclientv2.Workitemstatusupdate{}
workitemStatusMap, ok := workitemStatus.(map[string]interface{})
if !ok {
continue
}
// For the following if some attributes are not provided in terraform file we
// explicitly set the SDK attribute to nil to nullify its value in the API
if name, ok := workitemStatusMap["name"]; ok {
sdkWorkitemStatus.SetField("Name", platformclientv2.String(name.(string)))
}
if description, ok := workitemStatusMap["description"]; ok {
sdkWorkitemStatus.SetField("Description", platformclientv2.String(description.(string)))
}
if statusTransitionTime, ok := workitemStatusMap["status_transition_time"]; ok {
sdkWorkitemStatus.SetField("StatusTransitionTime", platformclientv2.String(statusTransitionTime.(string)))
} else {
sdkWorkitemStatus.SetField("StatusTransitionTime", nil)
}
if statusTransitionDelaySec, ok := workitemStatusMap["status_transition_delay_seconds"]; ok && statusTransitionDelaySec.(int) > 0 {
sdkWorkitemStatus.SetField("StatusTransitionDelaySeconds", platformclientv2.Int(statusTransitionDelaySec.(int)))
} else {
sdkWorkitemStatus.SetField("StatusTransitionDelaySeconds", nil)
}
if destinationStatuses, ok := workitemStatusMap["destination_status_names"]; ok {
statusIds := buildStatusIdFn(destinationStatuses.([]interface{}))
sdkWorkitemStatus.SetField("DestinationStatusIds", statusIds)
} else {
sdkWorkitemStatus.SetField("DestinationStatusIds", nil)
}
if defaultDestination, ok := workitemStatusMap["default_destination_status_name"]; ok {
defaultDestStatusId := getStatusIdFromNameFn(defaultDestination.(string))
sdkWorkitemStatus.SetField("DefaultDestinationStatusId", defaultDestStatusId)
} else {
sdkWorkitemStatus.SetField("DefaultDestinationStatusId", nil)
}
workitemStatussSlice = append(workitemStatussSlice, sdkWorkitemStatus)
}
return &workitemStatussSlice
}
// flattenWorkitemStatusReferences maps a Genesys Cloud *[]platformclientv2.Workitemstatusreference into a []interface{}
// Sadly the API only returns the ID in the ref object (even if the name is defined in the API model), so we still need the
// existingStatuses parameter to get the name for resolving back into resource data
func flattenWorkitemStatusReferences(workitemStatusReferences *[]platformclientv2.Workitemstatusreference, existingStatuses *[]platformclientv2.Workitemstatus) []interface{} {
if len(*workitemStatusReferences) == 0 {
return nil
}
var workitemStatusReferenceList []interface{}
for _, workitemStatusReference := range *workitemStatusReferences {
for _, existingStatus := range *existingStatuses {
if *workitemStatusReference.Id == *existingStatus.Id {
workitemStatusReferenceList = append(workitemStatusReferenceList, existingStatus.Name)
}
}
}
return workitemStatusReferenceList
}
// flattenWorkitemStatuss maps a Genesys Cloud *[]platformclientv2.Workitemstatus into a []interface{}
func flattenWorkitemStatuses(workitemStatuses *[]platformclientv2.Workitemstatus) []interface{} {
if len(*workitemStatuses) == 0 {
return nil
}
// Containing function for flattening because we need to use
// worktype statuses as reference for the method
flattenStatusRefsWithExisting := func(refs *[]platformclientv2.Workitemstatusreference) []interface{} {
return flattenWorkitemStatusReferences(refs, workitemStatuses)
}
var workitemStatusList []interface{}
for _, workitemStatus := range *workitemStatuses {
workitemStatusMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(workitemStatusMap, "id", workitemStatus.Id)
resourcedata.SetMapValueIfNotNil(workitemStatusMap, "name", workitemStatus.Name)
resourcedata.SetMapValueIfNotNil(workitemStatusMap, "category", workitemStatus.Category)
resourcedata.SetMapValueIfNotNil(workitemStatusMap, "description", workitemStatus.Description)
resourcedata.SetMapValueIfNotNil(workitemStatusMap, "status_transition_delay_seconds", workitemStatus.StatusTransitionDelaySeconds)
resourcedata.SetMapValueIfNotNil(workitemStatusMap, "status_transition_time", workitemStatus.StatusTransitionTime)
resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(workitemStatusMap, "destination_status_names", workitemStatus.DestinationStatuses, flattenStatusRefsWithExisting)
if workitemStatus.DefaultDestinationStatus != nil {
resourcedata.SetMapValueIfNotNil(workitemStatusMap, "default_destination_status_name", getStatusNameFromId(*workitemStatus.DefaultDestinationStatus.Id, workitemStatuses))
}
workitemStatusList = append(workitemStatusList, workitemStatusMap)
}
return workitemStatusList
}
// flattenRoutingSkillReferences maps a Genesys Cloud *[]platformclientv2.Routingskillreference into a []interface{}
func flattenRoutingSkillReferences(routingSkillReferences *[]platformclientv2.Routingskillreference) []interface{} {
if len(*routingSkillReferences) == 0 {
return nil
}
var routingSkillReferenceList []interface{}
for _, routingSkillReference := range *routingSkillReferences {
routingSkillReferenceList = append(routingSkillReferenceList, *routingSkillReference.Id)
}
return routingSkillReferenceList
}
// getStatusesForUpdateAndCreation takes a resource data list []interface{} of statuses and determines if they
// are to be created or just updated. Returns the two lists.
func getStatusesForUpdateAndCreation(statuses []interface{}, existingStatuses *[]platformclientv2.Workitemstatus) (forCreation []interface{}, forUpdate []interface{}) {
forCreation = make([]interface{}, 0)
forUpdate = make([]interface{}, 0)
// We will consider it the same status and update in-place if the name and the category matches.
// else, a new status will be created.
for _, status := range statuses {
statusMap := status.(map[string]interface{})
statusName, ok := statusMap["name"]
if !ok {
continue
}
statusCat, ok := statusMap["category"]
if !ok {
continue
}
toCreateNewStatus := true
// If the status matches an existing name and same category then we'll consider
// it the same and not create a new one.
for _, existingStatus := range *existingStatuses {
if *existingStatus.Name == statusName && *existingStatus.Category == statusCat {
toCreateNewStatus = false
break
}
}
if toCreateNewStatus {
forCreation = append(forCreation, status)
} else {
forUpdate = append(forUpdate, status)
}
}
return forCreation, forUpdate
}
// createWorktypeStatuses creates new statuses as defined in the config. This is just the initial
// creation as some statuses also need to be updated separately to build the destination status references.
func createWorktypeStatuses(ctx context.Context, proxy *taskManagementWorktypeProxy, worktypeId string, statuses []interface{}) (*[]platformclientv2.Workitemstatus, error) {
ret := []platformclientv2.Workitemstatus{}
sdkWorkitemStatusCreates := buildWorkitemStatusCreates(statuses)
for _, statusCreate := range *sdkWorkitemStatusCreates {
status, resp, err := proxy.createTaskManagementWorktypeStatus(ctx, worktypeId, &statusCreate)
if err != nil {
return nil, fmt.Errorf("failed to create worktype status %s: %v %v", *statusCreate.Name, err, resp)
}
ret = append(ret, *status)
}
return &ret, nil
}
// updateWorktypeStatuses updates the statuses of a worktype. There are two modes depending if the passed statuses
// is newly created or not. For newly created, we just check if there's any need to resolve references since they still
// have none. For existing statuses, they should be passed as already validated (has change - because API will return error
// if there's no change to the status), the method will not check it.
func updateWorktypeStatuses(ctx context.Context, proxy *taskManagementWorktypeProxy, worktypeId string, statuses []interface{}, isNewlyCreated bool) (*[]platformclientv2.Workitemstatus, error) {
ret := []platformclientv2.Workitemstatus{}
// Get all the worktype statuses so we'll have the new statuses for referencing
worktype, resp, err := proxy.getTaskManagementWorktypeById(ctx, worktypeId)
if err != nil {
return nil, fmt.Errorf("failed to get task management worktype %s: %v %v", worktypeId, err, resp)
}
// Update the worktype statuses as they need to build the "destination status" references
sdkWorkitemStatusUpdates := buildWorkitemStatusUpdates(statuses, worktype.Statuses)
for _, statusUpdate := range *sdkWorkitemStatusUpdates {
existingStatus := getStatusFromName(*statusUpdate.Name, worktype.Statuses)
if existingStatus.Id == nil {
return nil, fmt.Errorf("failed to update a status %s. Not found in the worktype %s: %v", *statusUpdate.Name, *worktype.Name, err)
}
// API does not allow updating a status with no actual change.
// For newly created statuses, update portion is only for resolving status references, so skip statuses where
// "destination statuses" and "default destination id" are not set.
if isNewlyCreated &&
(statusUpdate.DefaultDestinationStatusId == nil || *statusUpdate.DefaultDestinationStatusId == "") &&
(statusUpdate.DestinationStatusIds == nil || len(*statusUpdate.DestinationStatusIds) == 0) {
continue
}
status, resp, err := proxy.updateTaskManagementWorktypeStatus(ctx, *worktype.Id, *existingStatus.Id, &statusUpdate)
if err != nil {
return nil, fmt.Errorf("failed to update worktype status %s: %v %v", *statusUpdate.Name, err, resp)
}
ret = append(ret, *status)
}
return &ret, nil
}
// updateDefaultStatusName updates a worktype's default status name. This should be called after
// the statuses and their references have been finalized.
func updateDefaultStatusName(ctx context.Context, proxy *taskManagementWorktypeProxy, d *schema.ResourceData, worktypeId string) error {
worktype, resp, err := proxy.getTaskManagementWorktypeById(ctx, worktypeId)
if err != nil {
return fmt.Errorf("failed to get task management worktype: %s", err)
}
taskManagementWorktype := getWorktypeupdateFromResourceData(d, worktype.Statuses)
_, resp, err = proxy.updateTaskManagementWorktype(ctx, *worktype.Id, &taskManagementWorktype)
if err != nil {
return fmt.Errorf("failed to update worktype's default status name %s %v", err, resp)
}
return nil
}
// getStatusIdFromName gets the status id from name for the test util struct worktypeConfig
func (wt *worktypeConfig) getStatusIdFromName(name string) *string {
for _, s := range wt.statuses {
if s.name == name {
return &s.id
}
}
return nil
}
// GenerateWorktypeResourceBasic generates a terraform config string for a basic worktype
func GenerateWorktypeResourceBasic(resId, name, description, workbinResourceId, schemaResourceId, attrs string) string {
return fmt.Sprintf(`resource "%s" "%s" {
name = "%s"
description = "%s"
default_workbin_id = %s
schema_id = %s
%s
}
`, resourceName, resId, name, description, workbinResourceId, schemaResourceId, attrs)
}
package team
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The data_source_genesyscloud_team.go contains the data source implementation
for the resource.
*/
// dataSourceTeamRead retrieves by name the id in question
func dataSourceTeamRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := newTeamProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
teamId, retryable, resp, err := proxy.getTeamIdByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error searching team %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No team found with name %s", name), resp))
}
d.SetId(teamId)
return nil
})
}
package team
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_team_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *teamProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type createTeamFunc func(ctx context.Context, p *teamProxy, team *platformclientv2.Team) (*platformclientv2.Team, *platformclientv2.APIResponse, error)
type getAllTeamFunc func(ctx context.Context, p *teamProxy, name string) (*[]platformclientv2.Team, *platformclientv2.APIResponse, error)
type getTeamIdByNameFunc func(ctx context.Context, p *teamProxy, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
type getTeamByIdFunc func(ctx context.Context, p *teamProxy, id string) (team *platformclientv2.Team, response *platformclientv2.APIResponse, err error)
type updateTeamFunc func(ctx context.Context, p *teamProxy, id string, team *platformclientv2.Team) (*platformclientv2.Team, *platformclientv2.APIResponse, error)
type deleteTeamFunc func(ctx context.Context, p *teamProxy, id string) (response *platformclientv2.APIResponse, err error)
type createMembersFunc func(ctx context.Context, p *teamProxy, teamId string, members platformclientv2.Teammembers) (*platformclientv2.Teammemberaddlistingresponse, *platformclientv2.APIResponse, error)
type getMembersByIdFunc func(ctx context.Context, p *teamProxy, teamId string) (members *[]platformclientv2.Userreferencewithname, resp *platformclientv2.APIResponse, err error)
type deleteMembersFunc func(ctx context.Context, p *teamProxy, teamId string, memberId string) (response *platformclientv2.APIResponse, err error)
// teamProxy contains all of the methods that call genesys cloud APIs.
type teamProxy struct {
clientConfig *platformclientv2.Configuration
teamsApi *platformclientv2.TeamsApi
createTeamAttr createTeamFunc
getAllTeamAttr getAllTeamFunc
getTeamIdByNameAttr getTeamIdByNameFunc
getTeamByIdAttr getTeamByIdFunc
updateTeamAttr updateTeamFunc
deleteTeamAttr deleteTeamFunc
createMembersAttr createMembersFunc
getMembersByIdAttr getMembersByIdFunc
deleteMembersAttr deleteMembersFunc
}
// newTeamProxy initializes the team proxy with all of the data needed to communicate with Genesys Cloud
func newTeamProxy(clientConfig *platformclientv2.Configuration) *teamProxy {
api := platformclientv2.NewTeamsApiWithConfig(clientConfig)
return &teamProxy{
clientConfig: clientConfig,
teamsApi: api,
createTeamAttr: createTeamFn,
getAllTeamAttr: getAllTeamFn,
getTeamIdByNameAttr: getTeamIdByNameFn,
getTeamByIdAttr: getTeamByIdFn,
updateTeamAttr: updateTeamFn,
deleteTeamAttr: deleteTeamFn,
createMembersAttr: createMembersFn,
getMembersByIdAttr: getMembersByIdFn,
deleteMembersAttr: deleteMembersFn,
}
}
// getTeamProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTeamProxy(clientConfig *platformclientv2.Configuration) *teamProxy {
if internalProxy == nil {
internalProxy = newTeamProxy(clientConfig)
}
return internalProxy
}
// createTeam creates a Genesys Cloud team
func (p *teamProxy) createTeam(ctx context.Context, team *platformclientv2.Team) (*platformclientv2.Team, *platformclientv2.APIResponse, error) {
return p.createTeamAttr(ctx, p, team)
}
// getTeam retrieves all Genesys Cloud team
func (p *teamProxy) getAllTeam(ctx context.Context, name string) (*[]platformclientv2.Team, *platformclientv2.APIResponse, error) {
return p.getAllTeamAttr(ctx, p, name)
}
// getTeamIdByName returns a single Genesys Cloud team by a name
func (p *teamProxy) getTeamIdByName(ctx context.Context, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getTeamIdByNameAttr(ctx, p, name)
}
// getTeamById returns a single Genesys Cloud team by Id
func (p *teamProxy) getTeamById(ctx context.Context, id string) (team *platformclientv2.Team, resp *platformclientv2.APIResponse, err error) {
return p.getTeamByIdAttr(ctx, p, id)
}
// updateTeam updates a Genesys Cloud team
func (p *teamProxy) updateTeam(ctx context.Context, id string, team *platformclientv2.Team) (*platformclientv2.Team, *platformclientv2.APIResponse, error) {
return p.updateTeamAttr(ctx, p, id, team)
}
// deleteTeam deletes a Genesys Cloud team by Id
func (p *teamProxy) deleteTeam(ctx context.Context, id string) (resp *platformclientv2.APIResponse, err error) {
return p.deleteTeamAttr(ctx, p, id)
}
// createMembers creates a Genesys Cloud members
func (p *teamProxy) createMembers(ctx context.Context, teamId string, members platformclientv2.Teammembers) (*platformclientv2.Teammemberaddlistingresponse, *platformclientv2.APIResponse, error) {
return p.createMembersAttr(ctx, p, teamId, members)
}
// getMembersById returns a single Genesys Cloud members by Id
func (p *teamProxy) getMembersById(ctx context.Context, teamId string) (members *[]platformclientv2.Userreferencewithname, resp *platformclientv2.APIResponse, err error) {
return p.getMembersByIdAttr(ctx, p, teamId)
}
// deleteMembers deletes a Genesys Cloud members by Id
func (p *teamProxy) deleteMembers(ctx context.Context, teamId string, memberId string) (resp *platformclientv2.APIResponse, err error) {
return p.deleteMembersAttr(ctx, p, teamId, memberId)
}
// createTeamFn is an implementation function for creating a Genesys Cloud team
func createTeamFn(ctx context.Context, p *teamProxy, team *platformclientv2.Team) (*platformclientv2.Team, *platformclientv2.APIResponse, error) {
team, resp, err := p.teamsApi.PostTeams(*team)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create team: %s", err)
}
return team, resp, nil
}
// getAllTeamFn is the implementation for retrieving all team in Genesys Cloud
func getAllTeamFn(ctx context.Context, p *teamProxy, name string) (*[]platformclientv2.Team, *platformclientv2.APIResponse, error) {
var (
after string
allTeams []platformclientv2.Team
response *platformclientv2.APIResponse
)
const pageSize = 100
for i := 0; ; i++ {
teams, resp, getErr := p.teamsApi.GetTeams(pageSize, name, after, "", "")
response = resp
if getErr != nil {
return nil, resp, fmt.Errorf("Unable to find team entities %s", getErr)
}
if teams.Entities == nil || len(*teams.Entities) == 0 {
break
}
allTeams = append(allTeams, *teams.Entities...)
if teams.NextUri == nil || *teams.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*teams.NextUri, "after")
if err != nil {
return nil, resp, fmt.Errorf("unable to parse after cursor from teams next uri: %v", err)
}
if after == "" {
break
}
}
return &allTeams, response, nil
}
// getTeamIdByNameFn is an implementation of the function to get a Genesys Cloud team by name
func getTeamIdByNameFn(ctx context.Context, p *teamProxy, name string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error) {
teams, resp, err := getAllTeamFn(ctx, p, name)
if err != nil {
return "", false, resp, err
}
if len(*teams) == 0 {
return "", true, resp, fmt.Errorf("No team found with name %s", name)
}
for _, team := range *teams {
if *team.Name == name {
log.Printf("Retrieved the team id %s by name %s", *team.Id, name)
return *team.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find team with name %s", name)
}
// getTeamByIdFn is an implementation of the function to get a Genesys Cloud team by Id
func getTeamByIdFn(ctx context.Context, p *teamProxy, id string) (team *platformclientv2.Team, resp *platformclientv2.APIResponse, err error) {
team, resp, err = p.teamsApi.GetTeam(id)
if err != nil {
return nil, resp, fmt.Errorf("Failed to retrieve team by id %s: %s", id, err)
}
return team, resp, nil
}
// updateTeamFn is an implementation of the function to update a Genesys Cloud team
func updateTeamFn(ctx context.Context, p *teamProxy, id string, team *platformclientv2.Team) (*platformclientv2.Team, *platformclientv2.APIResponse, error) {
team, resp, err := p.teamsApi.PatchTeam(id, *team)
if err != nil {
return nil, resp, fmt.Errorf("Failed to update team: %s", err)
}
return team, resp, nil
}
// deleteTeamFn is an implementation function for deleting a Genesys Cloud team
func deleteTeamFn(ctx context.Context, p *teamProxy, id string) (resp *platformclientv2.APIResponse, err error) {
resp, err = p.teamsApi.DeleteTeam(id)
if err != nil {
return resp, fmt.Errorf("Failed to delete team: %s", err)
}
return resp, nil
}
// createMembersFn is an implementation function for creating a Genesys Cloud members
func createMembersFn(ctx context.Context, p *teamProxy, teamId string, members platformclientv2.Teammembers) (*platformclientv2.Teammemberaddlistingresponse, *platformclientv2.APIResponse, error) {
teamListingResponse, resp, err := p.teamsApi.PostTeamMembers(teamId, members)
if err != nil {
return nil, resp, fmt.Errorf("Failed to create members: %s", err)
}
return teamListingResponse, resp, nil
}
// getMembersByIdFn is an implementation of the function to get a Genesys Cloud members by Id
func getMembersByIdFn(_ context.Context, p *teamProxy, teamId string) (*[]platformclientv2.Userreferencewithname, *platformclientv2.APIResponse, error) {
var (
after string
allMembers []platformclientv2.Userreferencewithname
response *platformclientv2.APIResponse
)
const pageSize = 100
for {
members, resp, getErr := p.teamsApi.GetTeamMembers(teamId, pageSize, "", after, "")
response = resp
if getErr != nil {
return nil, resp, fmt.Errorf("unable to find member entities %s", getErr)
}
if members.Entities == nil || len(*members.Entities) == 0 {
break
}
allMembers = append(allMembers, *members.Entities...)
if members.NextUri == nil || *members.NextUri == "" {
break
}
after, err := util.GetQueryParamValueFromUri(*members.NextUri, "after")
if err != nil {
return nil, resp, fmt.Errorf("unable to parse after cursor from members next uri: %v", err)
}
if after == "" {
break
}
}
return &allMembers, response, nil
}
// deleteMembersFn is an implementation function for deleting a Genesys Cloud members
func deleteMembersFn(ctx context.Context, p *teamProxy, teamId string, memberIds string) (resp *platformclientv2.APIResponse, err error) {
resp, err = p.teamsApi.DeleteTeamMembers(teamId, memberIds)
if err != nil {
return resp, fmt.Errorf("Failed to delete members: %s", err)
}
return resp, nil
}
package team
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
/*
The resource_genesyscloud_team.go contains all of the methods that perform the core logic for a resource.
*/
// getAllAuthTeam retrieves all of the team via Terraform in the Genesys Cloud and is used for the exporter
func getAllAuthTeams(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
proxy := getTeamProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
teams, resp, err := proxy.getAllTeam(ctx, "")
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get team error: %s", err), resp)
}
for _, team := range *teams {
resources[*team.Id] = &resourceExporter.ResourceMeta{Name: *team.Name}
}
return resources, nil
}
// createTeam is used by the team resource to create Genesys cloud team
func createTeam(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTeamProxy(sdkConfig)
team := getTeamFromResourceData(d)
log.Printf("Creating team %s", *team.Name)
teamObj, resp, err := proxy.createTeam(ctx, &team)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create team %s error: %s", *team.Name, err), resp)
}
d.SetId(*teamObj.Id)
log.Printf("Created team %s", *teamObj.Id)
//adding members to the team
members, ok := d.GetOk("member_ids")
if ok {
if memberList := members.([]interface{}); len(memberList) > 0 {
diagErr := createMembers(ctx, *teamObj.Id, memberList, proxy)
if diagErr != nil {
return diagErr
}
}
}
return readTeam(ctx, d, meta)
}
// readTeam is used by the team resource to read a team from genesys cloud
func readTeam(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTeamProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTeam(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading team %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
team, resp, getErr := proxy.getTeamById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read team %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read team %s | error: %s", d.Id(), getErr), resp))
}
resourcedata.SetNillableValue(d, "name", team.Name)
resourcedata.SetNillableReferenceWritableDivision(d, "division_id", team.Division)
resourcedata.SetNillableValue(d, "description", team.Description)
// reading members
members, err := readMembers(ctx, d, proxy)
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(fmt.Errorf("failed to read members of the team %s : %s", d.Id(), err))
}
return retry.NonRetryableError(fmt.Errorf("failed to read members of the team %s : %s", d.Id(), err))
}
_ = d.Set("member_ids", members)
log.Printf("Read team %s %s", d.Id(), *team.Name)
return cc.CheckState(d)
})
}
// updateTeam is used by the team resource to update a team in Genesys Cloud
func updateTeam(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTeamProxy(sdkConfig)
team := getTeamFromResourceData(d)
log.Printf("Updating team %s", *team.Name)
teamObj, resp, err := proxy.updateTeam(ctx, d.Id(), &team)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update team %s error: %s", *team.Name, err), resp)
}
members := d.Get("member_ids")
memberList := members.([]interface{})
currentMembers, _ := readMembers(ctx, d, proxy)
if len(memberList) == 0 {
if len(currentMembers) > 0 {
log.Printf("removing all members from team %s", d.Id())
deleteMembers(ctx, d.Id(), currentMembers, proxy)
}
}
if len(memberList) > 0 {
removeMembers, addMembers := SliceDifferenceMembers(currentMembers, memberList)
if len(removeMembers) > 0 {
diagErr := deleteMembers(ctx, d.Id(), removeMembers, proxy)
if diagErr != nil {
return diagErr
}
}
if len(addMembers) > 0 {
diagErr := createMembers(ctx, d.Id(), addMembers, proxy)
if diagErr != nil {
return diagErr
}
}
}
log.Printf("Updated team %s", *teamObj.Id)
return readTeam(ctx, d, meta)
}
// deleteTeam is used by the team resource to delete a team from Genesys cloud
func deleteTeam(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTeamProxy(sdkConfig)
log.Printf("Deleting team %s", d.Id())
resp, err := proxy.deleteTeam(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete team %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError {
_, resp, err := proxy.getTeamById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted team %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting team %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("team %s still exists", d.Id()), resp))
})
}
// readMembers is used by the members resource to read a members from genesys cloud
func readMembers(ctx context.Context, d *schema.ResourceData, proxy *teamProxy) ([]interface{}, error) {
log.Printf("Reading members of team %s", d.Id())
teamMemberListing, resp, err := proxy.getMembersById(ctx, d.Id())
if err != nil {
log.Printf("unable to retrieve members of team %s : %s %v", d.Id(), err, resp)
return nil, err
}
log.Printf("Read members of team %s", d.Id())
if teamMemberListing != nil {
return flattenMemberIds(*teamMemberListing), nil
}
return nil, nil
}
// deleteMembers is used by the members resource to delete members from Genesys cloud
func deleteMembers(ctx context.Context, teamId string, memberList []interface{}, proxy *teamProxy) diag.Diagnostics {
resp, err := proxy.deleteMembers(ctx, teamId, convertMemberListtoString(memberList))
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update remove members from team %s error: %s", teamId, err), resp)
}
log.Printf("success removing members from team %s", teamId)
return nil
}
// createMembers is used by the members resource to create Genesys cloud members
func createMembers(ctx context.Context, teamId string, members []interface{}, proxy *teamProxy) diag.Diagnostics {
log.Printf("Adding members to team %s", teamId)
// API does not allow more than 25 members to be added at once, adding members in chunks of 25
const chunkSize = 25
var membersChunk []interface{}
for _, member := range members {
membersChunk = append(membersChunk, member)
if len(membersChunk)%chunkSize == 0 {
_, resp, err := proxy.createMembers(ctx, teamId, buildTeamMembers(membersChunk))
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to add members to team %s error: %s", teamId, err), resp)
}
membersChunk = nil
}
}
_, resp, err := proxy.createMembers(ctx, teamId, buildTeamMembers(membersChunk))
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to add members to team %s error: %s", teamId, err), resp)
}
log.Printf("Added members to team %s", teamId)
return nil
}
package team
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
/*
resource_genesycloud_team_schema.go holds four functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the team resource.
3. The datasource schema definitions for the team datasource.
4. The resource exporter configuration for the team exporter.
*/
const resourceName = "genesyscloud_team"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(regInstance registrar.Registrar) {
regInstance.RegisterResource(resourceName, ResourceTeam())
regInstance.RegisterDataSource(resourceName, DataSourceTeam())
regInstance.RegisterExporter(resourceName, TeamExporter())
}
// ResourceTeam registers the genesyscloud_team resource with Terraform
func ResourceTeam() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud team`,
CreateContext: provider.CreateWithPooledClient(createTeam),
ReadContext: provider.ReadWithPooledClient(readTeam),
UpdateContext: provider.UpdateWithPooledClient(updateTeam),
DeleteContext: provider.DeleteWithPooledClient(deleteTeam),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The team name",
Required: true,
Type: schema.TypeString,
},
"division_id": {
Description: "The division to which this entity belongs.",
Required: true,
Type: schema.TypeString,
},
"description": {
Description: "Team information.",
Optional: true,
Type: schema.TypeString,
},
`member_ids`: {
Description: `Specifies the members, No modifications to members will be made if not set. If empty all members will be deleted. If populated, only the populated members will be retained`,
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
// TeamExporter returns the resourceExporter object used to hold the genesyscloud_team exporter's config
func TeamExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthTeams),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"division_id": {RefType: "genesyscloud_auth_division"},
"member_ids": {RefType: "genesyscloud_user"},
},
}
}
// DataSourceTeam registers the genesyscloud_team data source
func DataSourceTeam() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud team data source. Select an team by name`,
ReadContext: provider.ReadWithPooledClient(dataSourceTeamRead),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Description: `team name`,
Type: schema.TypeString,
Required: true,
},
},
}
}
package team
import (
"log"
"math/rand"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func buildTeamMembers(teamMembers []interface{}) platformclientv2.Teammembers {
var teamMemberObject platformclientv2.Teammembers
members := make([]string, len(teamMembers))
for i, member := range teamMembers {
members[i] = member.(string)
}
teamMemberObject.MemberIds = &members
return teamMemberObject
}
func convertMemberListtoString(teamMembers []interface{}) string {
var memberList []string
for _, v := range teamMembers {
memberList = append(memberList, v.(string))
}
memberString := strings.Join(memberList, ",")
log.Printf("member list is %s", memberString)
return memberString
}
func flattenMemberIds(teamEntityListing []platformclientv2.Userreferencewithname) []interface{} {
memberList := []interface{}{}
if len(teamEntityListing) == 0 {
return nil
}
for _, teamEntity := range teamEntityListing {
memberList = append(memberList, *teamEntity.Id)
}
return memberList
}
func SliceDifferenceMembers(current, target []interface{}) ([]interface{}, []interface{}) {
var remove []interface{}
var add []interface{}
keysTarget := make(map[interface{}]bool)
keysCurrent := make(map[interface{}]bool)
for _, item := range target {
keysTarget[item] = true
}
for _, item := range current {
keysCurrent[item] = true
}
for _, item := range current {
if _, found := keysTarget[item]; !found {
remove = append(remove, item)
}
}
for _, item := range target {
if _, found := keysCurrent[item]; !found {
add = append(add, item)
}
}
return remove, add
}
// getTeamFromResourceData maps data from schema ResourceData object to a platformclientv2.Team
func getTeamFromResourceData(d *schema.ResourceData) platformclientv2.Team {
name := d.Get("name").(string)
division := d.Get("division_id").(string)
return platformclientv2.Team{
Name: &name,
Division: &platformclientv2.Writabledivision{Id: &division},
Description: platformclientv2.String(d.Get("description").(string)),
}
}
func randString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
s := make([]byte, n)
for i := range s {
s[i] = letters[rand.Intn(len(letters))]
}
return string(s)
}
package telephony
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func DataSourceTrunkBaseSettings() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Trunk Base Settings. Select a trunk base settings by name",
ReadContext: provider.ReadWithPooledClient(dataSourceTrunkBaseSettingsRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Trunk Base Settings name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceTrunkBaseSettingsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
trunkBaseSettings, resp, getErr := getTelephonyProvidersEdgesTrunkbasesettings(sdkConfig, pageNum, pageSize, name)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting trunk base settings %s | error: %s", name, getErr), resp))
}
if trunkBaseSettings.Entities == nil || len(*trunkBaseSettings.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No trunkBaseSettings found with name %s", name), resp))
}
for _, trunkBaseSetting := range *trunkBaseSettings.Entities {
if trunkBaseSetting.Name != nil && *trunkBaseSetting.Name == name &&
trunkBaseSetting.State != nil && *trunkBaseSetting.State != "deleted" {
d.SetId(*trunkBaseSetting.Id)
return nil
}
}
}
})
}
package telephony
import (
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource("genesyscloud_telephony_providers_edges_trunkbasesettings", DataSourceTrunkBaseSettings())
l.RegisterResource("genesyscloud_telephony_providers_edges_trunkbasesettings", ResourceTrunkBaseSettings())
l.RegisterExporter("genesyscloud_telephony_providers_edges_trunkbasesettings", TrunkBaseSettingsExporter())
}
package telephony
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
const (
resourceName = "genesyscloud_telephony_providers_edges_trunkbasesettings"
)
func ResourceTrunkBaseSettings() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Trunk Base Settings",
CreateContext: provider.CreateWithPooledClient(createTrunkBaseSettings),
ReadContext: provider.ReadWithPooledClient(readTrunkBaseSettings),
UpdateContext: provider.UpdateWithPooledClient(updateTrunkBaseSettings),
DeleteContext: provider.DeleteWithPooledClient(deleteTrunkBaseSettings),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"state": {
Description: "The resource's state.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Description: "The resource's description.",
Type: schema.TypeString,
Optional: true,
},
"trunk_meta_base_id": {
Description: "The meta-base this trunk is based on.",
Type: schema.TypeString,
Required: true,
},
"properties": {
Description: "trunk base settings properties",
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"trunk_type": {
Description: "The type of this trunk base.Valid values: EXTERNAL, PHONE, EDGE.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"EXTERNAL", "PHONE", "EDGE"}, false),
},
"managed": {
Description: "Is this trunk being managed remotely. This property is synchronized with the managed property of the Edge Group to which it is assigned.",
Type: schema.TypeBool,
Optional: true,
},
"inbound_site_id": {
Description: "The site to which inbound calls will be routed. Only valid for External BYOC Trunks.",
Type: schema.TypeString,
Optional: true,
},
},
CustomizeDiff: util.CustomizeTrunkBaseSettingsPropertiesDiff,
}
}
func createTrunkBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
trunkMetaBaseString := d.Get("trunk_meta_base_id").(string)
trunkMetaBase := util.BuildSdkDomainEntityRef(d, "trunk_meta_base_id")
inboundSiteString := d.Get("inbound_site_id").(string)
properties := util.BuildTelephonyProperties(d)
trunkType := d.Get("trunk_type").(string)
managed := d.Get("managed").(bool)
trunkBase := platformclientv2.Trunkbase{
Name: &name,
TrunkMetabase: trunkMetaBase,
TrunkType: &trunkType,
Managed: &managed,
Properties: properties,
}
validationInboundSite, errorInboundSite := ValidateInboundSiteSettings(inboundSiteString, trunkMetaBaseString)
if validationInboundSite && errorInboundSite == nil {
inboundSite := util.BuildSdkDomainEntityRef(d, "inbound_site_id")
trunkBase.InboundSite = inboundSite
}
if errorInboundSite != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to create trunk base settings %s", name), errorInboundSite)
}
if description != "" {
trunkBase.Description = &description
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
log.Printf("Creating trunk base settings %s", name)
trunkBaseSettings, resp, err := edgesAPI.PostTelephonyProvidersEdgesTrunkbasesettings(trunkBase)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create trunk base settings %s error: %s", name, err), resp)
}
d.SetId(*trunkBaseSettings.Id)
log.Printf("Created trunk base settings %s", *trunkBaseSettings.Id)
return readTrunkBaseSettings(ctx, d, meta)
}
func updateTrunkBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
trunkMetaBaseString := d.Get("trunk_meta_base_id").(string)
trunkMetaBase := util.BuildSdkDomainEntityRef(d, "trunk_meta_base_id")
inboundSiteString := d.Get("inbound_site_id").(string)
properties := util.BuildTelephonyProperties(d)
trunkType := d.Get("trunk_type").(string)
managed := d.Get("managed").(bool)
id := d.Id()
trunkBase := platformclientv2.Trunkbase{
Id: &id,
Name: &name,
TrunkMetabase: trunkMetaBase,
TrunkType: &trunkType,
Managed: &managed,
Properties: properties,
}
validationInboundSite, errorInboundSite := ValidateInboundSiteSettings(inboundSiteString, trunkMetaBaseString)
if validationInboundSite && errorInboundSite == nil {
inboundSite := util.BuildSdkDomainEntityRef(d, "inbound_site_id")
trunkBase.InboundSite = inboundSite
}
if errorInboundSite != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to update trunk base settings %s", name), errorInboundSite)
}
if description != "" {
trunkBase.Description = &description
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get the latest version of the setting
trunkBaseSettings, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesTrunkbasesetting(d.Id(), true)
if getErr != nil {
if util.IsStatus404(resp) {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("The trunk base settings does not exist %s error: %s", d.Id(), getErr), resp)
}
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read trunk base settings %s error: %s", d.Id(), getErr), resp)
}
trunkBase.Version = trunkBaseSettings.Version
log.Printf("Updating trunk base settings %s", name)
trunkBaseSettings, resp, err := edgesAPI.PutTelephonyProvidersEdgesTrunkbasesetting(d.Id(), trunkBase)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update trunk base settings %s error: %s", name, err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
// Get the latest version of the setting
trunkBaseSettings, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesTrunkbasesetting(d.Id(), true)
if getErr != nil {
if util.IsStatus404(resp) {
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read trunk base settings %s error: %s", d.Id(), getErr), resp)
}
trunkBase.Version = trunkBaseSettings.Version
log.Printf("Updating trunk base settings %s", name)
trunkBaseSettings, resp, err := edgesAPI.PutTelephonyProvidersEdgesTrunkbasesetting(d.Id(), trunkBase)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update trunk base settings %s error: %s", d.Id(), err), resp)
}
log.Printf("Updated trunk base settings %s", *trunkBaseSettings.Id)
return readTrunkBaseSettings(ctx, d, meta)
}
func readTrunkBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTrunkBaseSettings(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading trunk base settings %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
trunkBaseSettings, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesTrunkbasesetting(d.Id(), true)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read trunk base settings %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read trunk base settings %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *trunkBaseSettings.Name)
d.Set("state", *trunkBaseSettings.State)
if trunkBaseSettings.Description != nil {
d.Set("description", *trunkBaseSettings.Description)
}
if trunkBaseSettings.Managed != nil {
d.Set("managed", *trunkBaseSettings.Managed)
}
// check if Id is null or not for both metabase and inboundsite
if trunkBaseSettings.TrunkMetabase != nil {
d.Set("trunk_meta_base_id", *trunkBaseSettings.TrunkMetabase.Id)
}
if trunkBaseSettings.InboundSite != nil {
d.Set("inbound_site_id", *trunkBaseSettings.InboundSite.Id)
}
d.Set("trunk_type", *trunkBaseSettings.TrunkType)
d.Set("properties", nil)
if trunkBaseSettings.Properties != nil {
properties, err := util.FlattenTelephonyProperties(trunkBaseSettings.Properties)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
d.Set("properties", properties)
}
log.Printf("Read trunk base settings %s %s", d.Id(), *trunkBaseSettings.Name)
return cc.CheckState(d)
})
}
func deleteTrunkBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting trunk base settings")
resp, err := edgesAPI.DeleteTelephonyProvidersEdgesTrunkbasesetting(d.Id())
if err != nil {
if util.IsStatus404(resp) {
// trunk base settings not found, goal achieved!
return nil, nil
}
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete trunk base settings %s error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
trunkBaseSettings, resp, err := edgesAPI.GetTelephonyProvidersEdgesTrunkbasesetting(d.Id(), true)
if err != nil {
if util.IsStatus404(resp) {
// trunk base settings deleted
log.Printf("Deleted trunk base settings %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting trunk base settings %s | error: %s", d.Id(), err), resp))
}
if trunkBaseSettings.State != nil && *trunkBaseSettings.State == "deleted" {
// trunk base settings deleted
log.Printf("Deleted trunk base settings %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("trunk base settings %s still exists", d.Id()), resp))
})
}
func getAllTrunkBaseSettings(ctx context.Context, sdkConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
for pageNum := 1; ; pageNum++ {
const pageSize = 100
trunkBaseSettings, resp, getErr := getTelephonyProvidersEdgesTrunkbasesettings(sdkConfig, pageNum, pageSize, "")
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of trunk base settings error: %s", getErr), resp)
}
if trunkBaseSettings.Entities == nil || len(*trunkBaseSettings.Entities) == 0 {
break
}
for _, trunkBaseSetting := range *trunkBaseSettings.Entities {
if trunkBaseSetting.State != nil && *trunkBaseSetting.State != "deleted" {
resources[*trunkBaseSetting.Id] = &resourceExporter.ResourceMeta{Name: *trunkBaseSetting.Name}
}
}
}
return resources, nil
}
// The SDK function is too cumbersome because of the various boolean query parameters.
// This function was written in order to leave them out and make a single API call
func getTelephonyProvidersEdgesTrunkbasesettings(sdkConfig *platformclientv2.Configuration, pageNumber int, pageSize int, name string) (*platformclientv2.Trunkbaseentitylisting, *platformclientv2.APIResponse, error) {
headerParams := make(map[string]string)
if sdkConfig.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + sdkConfig.AccessToken
}
// add default headers if any
for key := range sdkConfig.DefaultHeader {
headerParams[key] = sdkConfig.DefaultHeader[key]
}
queryParams := make(map[string]string)
queryParams["pageNumber"] = sdkConfig.APIClient.ParameterToString(pageNumber, "")
queryParams["pageSize"] = sdkConfig.APIClient.ParameterToString(pageSize, "")
if name != "" {
queryParams["name"] = sdkConfig.APIClient.ParameterToString(name, "")
}
// to determine the Content-Type header
httpContentTypes := []string{"application/json"}
// set Content-Type header
httpContentType := sdkConfig.APIClient.SelectHeaderContentType(httpContentTypes)
if httpContentType != "" {
headerParams["Content-Type"] = httpContentType
}
// set Accept header
httpHeaderAccept := sdkConfig.APIClient.SelectHeaderAccept([]string{
"application/json",
})
if httpHeaderAccept != "" {
headerParams["Accept"] = httpHeaderAccept
}
var successPayload *platformclientv2.Trunkbaseentitylisting
path := sdkConfig.BasePath + "/api/v2/telephony/providers/edges/trunkbasesettings"
response, err := sdkConfig.APIClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil)
if err != nil {
return nil, nil, err
}
if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal(response.RawBody, &successPayload)
}
return successPayload, response, err
}
func ValidateInboundSiteSettings(inboundSiteString string, trunkBaseMetaId string) (bool, error) {
externalTrunkName := "external_sip_pcv_byoc"
if len(inboundSiteString) == 0 && strings.Contains(trunkBaseMetaId, externalTrunkName) {
return false, errors.New("inboundSite is required for external BYOC trunks")
}
if len(inboundSiteString) > 0 && !strings.Contains(trunkBaseMetaId, externalTrunkName) {
return false, errors.New("inboundSite should be set for external BYOC trunks only")
}
if len(inboundSiteString) > 0 && strings.Contains(trunkBaseMetaId, externalTrunkName) {
return true, nil
}
return false, nil
}
func TrunkBaseSettingsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllTrunkBaseSettings),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{},
JsonEncodeAttributes: []string{"properties"},
}
}
func GenerateTrunkBaseSettingsResourceWithCustomAttrs(
trunkBaseSettingsRes,
name,
description,
trunkMetaBaseId,
trunkType string,
managed bool,
otherAttrs ...string) string {
return fmt.Sprintf(`resource "genesyscloud_telephony_providers_edges_trunkbasesettings" "%s" {
name = "%s"
description = "%s"
trunk_meta_base_id = "%s"
trunk_type = "%s"
managed = %v
%s
}
`, trunkBaseSettingsRes, name, description, trunkMetaBaseId, trunkType, managed, strings.Join(otherAttrs, "\n"))
}
package telephony_providers_edges_did
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// dataSourceDidRead retrieves by DID ID by DID number
func dataSourceDidRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
proxy := getTelephonyProvidersEdgesDidProxy(sdkConfig)
didPhoneNumber := d.Get("phone_number").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
id, retryable, resp, err := proxy.getTelephonyProvidersEdgesDidIdByDid(ctx, didPhoneNumber)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get telephony providers Edges %s", err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get telephony providers edges %s", err), resp))
}
d.SetId(id)
return nil
})
}
package telephony_providers_edges_did
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_telephony_providers_edges_did_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *telephonyProvidersEdgesDidProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getTelephonyProvidersEdgesDidIdByDidFunc func(ctx context.Context, t *telephonyProvidersEdgesDidProxy, did string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
// telephonyProvidersEdgesDidProxy contains all of the methods that call genesys cloud APIs.
type telephonyProvidersEdgesDidProxy struct {
clientConfig *platformclientv2.Configuration
telephonyApi *platformclientv2.TelephonyProvidersEdgeApi
getTelephonyProvidersEdgesDidIdByDidAttr getTelephonyProvidersEdgesDidIdByDidFunc
}
// newTelephonyProvidersEdgesDidProxy initializes the proxy with all data needed to communicate with Genesys Cloud
func newTelephonyProvidersEdgesDidProxy(clientConfig *platformclientv2.Configuration) *telephonyProvidersEdgesDidProxy {
api := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
return &telephonyProvidersEdgesDidProxy{
clientConfig: clientConfig,
telephonyApi: api,
getTelephonyProvidersEdgesDidIdByDidAttr: getTelephonyProvidersEdgesDidIdByDidFn,
}
}
// getTelephonyProvidersEdgesDidProxy acts as a singleton for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTelephonyProvidersEdgesDidProxy(clientConfig *platformclientv2.Configuration) *telephonyProvidersEdgesDidProxy {
if internalProxy == nil {
internalProxy = newTelephonyProvidersEdgesDidProxy(clientConfig)
}
return internalProxy
}
// getTelephonyProvidersEdgesDidIdByDid gets a Genesys Cloud telephony DID ID by DID number
func (t *telephonyProvidersEdgesDidProxy) getTelephonyProvidersEdgesDidIdByDid(ctx context.Context, did string) (string, bool, *platformclientv2.APIResponse, error) {
return t.getTelephonyProvidersEdgesDidIdByDidAttr(ctx, t, did)
}
// getTelephonyProvidersEdgesDidIdByDidFn is an implementation function for getting a telephony DID ID by DID number.
func getTelephonyProvidersEdgesDidIdByDidFn(_ context.Context, t *telephonyProvidersEdgesDidProxy, did string) (string, bool, *platformclientv2.APIResponse, error) {
const pageSize = 100
pageNum := 1
dids, resp, getErr := t.telephonyApi.GetTelephonyProvidersEdgesDids(pageSize, pageNum, "", "", did, "", "", nil)
if getErr != nil {
return "", false, resp, fmt.Errorf("error requesting list of DIDs: %s", getErr)
}
for pageNum := 1; pageNum <= *dids.PageCount; pageNum++ {
dids, resp, getErr := t.telephonyApi.GetTelephonyProvidersEdgesDids(pageSize, pageNum, "", "", did, "", "", nil)
if getErr != nil {
return "", false, resp, fmt.Errorf("error requesting list of DIDs: %s", getErr)
}
if dids.Entities == nil || len(*dids.Entities) == 0 {
break
}
for _, entity := range *dids.Entities {
if *entity.PhoneNumber == did {
return *entity.Id, false, resp, nil
}
}
}
return "", true, resp, fmt.Errorf("failed to find ID of did number '%s'", did)
}
package telephony_providers_edges_did
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
const resourceName = "genesyscloud_telephony_providers_edges_did"
// SetRegistrar registers all resources, data sources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceDid())
}
// DataSourceDid registers the genesyscloud_telephony_providers_edges_did data source
func DataSourceDid() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud DID. The identifier is the E-164 phone number.",
ReadContext: provider.ReadWithPooledClient(dataSourceDidRead),
Schema: map[string]*schema.Schema{
"phone_number": {
Description: "Phone number for the DID.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
},
}
}
package telephony_providers_edges_did_pool
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// dataSourceDidPoolRead retrieves the did pool id using the start and end number
func dataSourceDidPoolRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
proxy := getTelephonyDidPoolProxy(sdkConfig)
didPoolStartPhoneNumber := d.Get("start_phone_number").(string)
didPoolEndPhoneNumber := d.Get("end_phone_number").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
id, retryable, resp, err := proxy.getTelephonyDidPoolIdByStartAndEndNumber(ctx, didPoolStartPhoneNumber, didPoolEndPhoneNumber)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get telephony DID pool %s", err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get telephony DID pool %s", err), resp))
}
d.SetId(id)
return nil
})
}
package telephony_providers_edges_did_pool
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_telephony_providers_edges_did_pool_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds an instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *telephonyDidPoolProxy
// Type definitions for each func on our proxy, so we can easily mock them out later
type createTelephonyDidPool func(ctx context.Context, t *telephonyDidPoolProxy, didPool *platformclientv2.Didpool) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error)
type getTelephonyDidPoolById func(context.Context, *telephonyDidPoolProxy, string) (didPool *platformclientv2.Didpool, resp *platformclientv2.APIResponse, err error)
type updateTelephonyDidPool func(context.Context, *telephonyDidPoolProxy, string, *platformclientv2.Didpool) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error)
type deleteTelephonyDidPool func(context.Context, *telephonyDidPoolProxy, string) (*platformclientv2.APIResponse, error)
type getTelephonyDidPoolIdByStartAndEndNumber func(ctx context.Context, t *telephonyDidPoolProxy, start, end string) (id string, retryable bool, resp *platformclientv2.APIResponse, err error)
type getAllTelephonyDidPools func(context.Context, *telephonyDidPoolProxy) (*[]platformclientv2.Didpool, *platformclientv2.APIResponse, error)
// telephonyDidPoolProxy contains all methods that call genesys cloud APIs.
type telephonyDidPoolProxy struct {
clientConfig *platformclientv2.Configuration
telephonyApi *platformclientv2.TelephonyProvidersEdgeApi
createTelephonyDidPoolAttr createTelephonyDidPool
getTelephonyDidPoolByIdAttr getTelephonyDidPoolById
updateEdgesDidPoolAttr updateTelephonyDidPool
deleteTelephonyDidPoolAttr deleteTelephonyDidPool
getTelephonyDidPoolIdByStartAndEndNumberAttr getTelephonyDidPoolIdByStartAndEndNumber
getAllTelephonyDidPoolsAttr getAllTelephonyDidPools
}
// newTelephonyProvidersEdgesDidPoolProxy initializes the proxy with all data needed to communicate with Genesys Cloud
func newTelephonyProvidersEdgesDidPoolProxy(clientConfig *platformclientv2.Configuration) *telephonyDidPoolProxy {
api := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
return &telephonyDidPoolProxy{
clientConfig: clientConfig,
telephonyApi: api,
createTelephonyDidPoolAttr: createTelephonyDidPoolFn,
getTelephonyDidPoolByIdAttr: getTelephonyDidPoolByIdFn,
updateEdgesDidPoolAttr: updateEdgesDidPoolFn,
deleteTelephonyDidPoolAttr: deleteTelephonyDidPoolFn,
getTelephonyDidPoolIdByStartAndEndNumberAttr: getTelephonyDidPoolIdByStartAndEndNumberFn,
getAllTelephonyDidPoolsAttr: getAllTelephonyDidPoolsFn,
}
}
// getTelephonyDidPoolProxy acts as a singleton for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTelephonyDidPoolProxy(clientConfig *platformclientv2.Configuration) *telephonyDidPoolProxy {
if internalProxy == nil {
internalProxy = newTelephonyProvidersEdgesDidPoolProxy(clientConfig)
}
return internalProxy
}
// createTelephonyDidPool creates a Genesys Cloud did pool
func (t *telephonyDidPoolProxy) createTelephonyDidPool(ctx context.Context, didPool *platformclientv2.Didpool) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
return t.createTelephonyDidPoolAttr(ctx, t, didPool)
}
// getTelephonyDidPoolById reads a Genesys Cloud did pool by id
func (t *telephonyDidPoolProxy) getTelephonyDidPoolById(ctx context.Context, id string) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
return t.getTelephonyDidPoolByIdAttr(ctx, t, id)
}
// updateTelephonyDidPool update a Genesys Cloud did pool
func (t *telephonyDidPoolProxy) updateTelephonyDidPool(ctx context.Context, id string, didPool *platformclientv2.Didpool) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
return t.updateEdgesDidPoolAttr(ctx, t, id, didPool)
}
// deleteTelephonyDidPool delete a Genesys Cloud did pool
func (t *telephonyDidPoolProxy) deleteTelephonyDidPool(ctx context.Context, id string) (*platformclientv2.APIResponse, error) {
return t.deleteTelephonyDidPoolAttr(ctx, t, id)
}
// getTelephonyDidPoolIdByStartAndEndNumber find a Genesys Cloud did pool id using the start and end number
func (t *telephonyDidPoolProxy) getTelephonyDidPoolIdByStartAndEndNumber(ctx context.Context, start, end string) (string, bool, *platformclientv2.APIResponse, error) {
return t.getTelephonyDidPoolIdByStartAndEndNumberAttr(ctx, t, start, end)
}
// getAllTelephonyDidPools read all Genesys Cloud did pools
func (t *telephonyDidPoolProxy) getAllTelephonyDidPools(ctx context.Context) (*[]platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
return t.getAllTelephonyDidPoolsAttr(ctx, t)
}
// createTelephonyDidPoolFn is an implementation function for creating a Genesys Cloud did pool
func createTelephonyDidPoolFn(_ context.Context, t *telephonyDidPoolProxy, didPool *platformclientv2.Didpool) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
postDidPool, resp, err := t.telephonyApi.PostTelephonyProvidersEdgesDidpools(*didPool)
if err != nil {
return nil, resp, err
}
return postDidPool, resp, nil
}
// getTelephonyDidPoolByIdFn is an implementation function for reading a Genesys Cloud did pool by ID
func getTelephonyDidPoolByIdFn(_ context.Context, t *telephonyDidPoolProxy, id string) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
didPool, resp, err := t.telephonyApi.GetTelephonyProvidersEdgesDidpool(id)
if err != nil {
return nil, resp, err
}
return didPool, resp, nil
}
// updateEdgesDidPoolFn is an implementation function for updating a Genesys Cloud did pool
func updateEdgesDidPoolFn(_ context.Context, t *telephonyDidPoolProxy, id string, didPool *platformclientv2.Didpool) (*platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
updatedDidPool, resp, err := t.telephonyApi.PutTelephonyProvidersEdgesDidpool(id, *didPool)
if err != nil {
return nil, resp, err
}
return updatedDidPool, resp, nil
}
// deleteTelephonyDidPoolFn is an implementation function for deleting a Genesys Cloud did pool
func deleteTelephonyDidPoolFn(_ context.Context, t *telephonyDidPoolProxy, id string) (*platformclientv2.APIResponse, error) {
resp, err := t.telephonyApi.DeleteTelephonyProvidersEdgesDidpool(id)
return resp, err
}
// getAllTelephonyDidPoolsFn is an implementation function for reading all Genesys Cloud did pools
func getAllTelephonyDidPoolsFn(_ context.Context, t *telephonyDidPoolProxy) (*[]platformclientv2.Didpool, *platformclientv2.APIResponse, error) {
var (
allDidPools []platformclientv2.Didpool
pageCount int
pageNum = 1
)
const pageSize = 100
didPools, resp, getErr := t.telephonyApi.GetTelephonyProvidersEdgesDidpools(pageSize, pageNum, "", nil)
if getErr != nil {
return nil, resp, getErr
}
pageCount = *didPools.PageCount
if didPools.Entities != nil && len(*didPools.Entities) > 0 {
allDidPools = append(allDidPools, *didPools.Entities...)
}
if pageCount < 2 {
return &allDidPools, resp, nil
}
for pageNum := 2; pageNum <= pageCount; pageNum++ {
didPools, resp, getErr := t.telephonyApi.GetTelephonyProvidersEdgesDidpools(pageSize, pageNum, "", nil)
if getErr != nil {
return nil, resp, getErr
}
if didPools.Entities == nil || len(*didPools.Entities) == 0 {
break
}
allDidPools = append(allDidPools, *didPools.Entities...)
}
return &allDidPools, resp, nil
}
// getTelephonyDidPoolIdByStartAndEndNumberFn is an implementation function for finding a Genesys Cloud did pool using the start and end number
func getTelephonyDidPoolIdByStartAndEndNumberFn(ctx context.Context, t *telephonyDidPoolProxy, start, end string) (string, bool, *platformclientv2.APIResponse, error) {
allDidPools, resp, err := getAllTelephonyDidPoolsFn(ctx, t)
if err != nil {
return "", false, resp, fmt.Errorf("failed to read did pools: %v", err)
}
for _, didPool := range *allDidPools {
if didPool.StartPhoneNumber != nil && *didPool.StartPhoneNumber == start &&
didPool.EndPhoneNumber != nil && *didPool.EndPhoneNumber == end &&
didPool.State != nil && *didPool.State != "deleted" {
return *didPool.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("failed to find DID pool with start phone number '%s' and end phone number '%s'", start, end)
}
package telephony_providers_edges_did_pool
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
// getAllDidPools retrieves all DID pools and is used for the exporter
func getAllDidPools(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
proxy := getTelephonyDidPoolProxy(clientConfig)
didPools, resp, err := proxy.getAllTelephonyDidPools(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get did pools error: %s", err), resp)
}
for _, didPool := range *didPools {
if didPool.State != nil && *didPool.State != "deleted" {
resources[*didPool.Id] = &resourceExporter.ResourceMeta{Name: *didPool.StartPhoneNumber}
}
}
return resources, nil
}
// createDidPool is used by the resource to create a Genesys Cloud DID pool
func createDidPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
startPhoneNumber := d.Get("start_phone_number").(string)
endPhoneNumber := d.Get("end_phone_number").(string)
description := d.Get("description").(string)
comments := d.Get("comments").(string)
poolProvider := d.Get("pool_provider").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTelephonyDidPoolProxy(sdkConfig)
didPool := &platformclientv2.Didpool{
StartPhoneNumber: &startPhoneNumber,
EndPhoneNumber: &endPhoneNumber,
Description: &description,
Comments: &comments,
Provider: &poolProvider,
}
log.Printf("Creating DID pool %s", startPhoneNumber)
createdDidPool, resp, err := proxy.createTelephonyDidPool(ctx, didPool)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create DID pool %s error: %s", startPhoneNumber, err), resp)
}
d.SetId(*createdDidPool.Id)
log.Printf("Created DID pool %s %s", startPhoneNumber, *createdDidPool.Id)
return readDidPool(ctx, d, meta)
}
// readDidPool is used by the resource to read a Genesys Cloud DID pool
func readDidPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTelephonyDidPoolProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTelephonyDidPool(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading DID pool %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
didPool, resp, getErr := proxy.getTelephonyDidPoolById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read DID pool %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read DID pool %s | error: %s", d.Id(), getErr), resp))
}
if didPool.State != nil && *didPool.State == "deleted" {
d.SetId("")
return nil
}
_ = d.Set("start_phone_number", *didPool.StartPhoneNumber)
_ = d.Set("end_phone_number", *didPool.EndPhoneNumber)
resourcedata.SetNillableValue(d, "description", didPool.Description)
resourcedata.SetNillableValue(d, "comments", didPool.Comments)
resourcedata.SetNillableValue(d, "pool_provider", didPool.Provider)
log.Printf("Read DID pool %s %s", d.Id(), *didPool.StartPhoneNumber)
return cc.CheckState(d)
})
}
// updateDidPool is used by the resource to update a Genesys Cloud DID pool
func updateDidPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
startPhoneNumber := d.Get("start_phone_number").(string)
endPhoneNumber := d.Get("end_phone_number").(string)
description := d.Get("description").(string)
comments := d.Get("comments").(string)
poolProvider := d.Get("pool_provider").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTelephonyDidPoolProxy(sdkConfig)
didPoolBody := &platformclientv2.Didpool{
StartPhoneNumber: &startPhoneNumber,
EndPhoneNumber: &endPhoneNumber,
Description: &description,
Comments: &comments,
Provider: &poolProvider,
}
log.Printf("Updating DID pool %s", d.Id())
if _, resp, err := proxy.updateTelephonyDidPool(ctx, d.Id(), didPoolBody); err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update DID pool %s error: %s", startPhoneNumber, err), resp)
}
log.Printf("Updated DID pool %s", d.Id())
return readDidPool(ctx, d, meta)
}
// deleteDidPool is used by the resource to delete a Genesys Cloud DID pool
func deleteDidPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
startPhoneNumber := d.Get("start_phone_number").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTelephonyDidPoolProxy(sdkConfig)
// DEVTOOLING-317: Unable to delete DID pool with a number assigned, retrying on HTTP 409
diagErr := util.RetryWhen(util.IsStatus409, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Deleting DID pool with starting number %s", startPhoneNumber)
resp, err := proxy.deleteTelephonyDidPool(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete DID pool %s error: %s", startPhoneNumber, err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
didPool, resp, err := proxy.getTelephonyDidPoolById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// DID pool deleted
log.Printf("Deleted DID pool %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting DID pool %s | error: %s", d.Id(), err), resp))
}
if didPool.State != nil && *didPool.State == "deleted" {
// DID pool deleted
log.Printf("Deleted DID pool %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("DID pool %s still exists", d.Id()), resp))
})
}
package telephony_providers_edges_did_pool
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
const resourceName = "genesyscloud_telephony_providers_edges_did_pool"
// SetRegistrar registers all resources, data sources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceDidPool())
l.RegisterResource(resourceName, ResourceTelephonyDidPool())
l.RegisterExporter(resourceName, TelephonyDidPoolExporter())
}
// TelephonyDidPoolExporter returns the resourceExporter object used to hold the genesyscloud_telephony_providers_edges_did_pool exporter's config
func TelephonyDidPoolExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllDidPools),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
// ResourceTelephonyDidPool registers the genesyscloud_telephony_providers_edges_did_pool resource with Terraform
func ResourceTelephonyDidPool() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud DID Pool",
CreateContext: provider.CreateWithPooledClient(createDidPool),
ReadContext: provider.ReadWithPooledClient(readDidPool),
UpdateContext: provider.UpdateWithPooledClient(updateDidPool),
DeleteContext: provider.DeleteWithPooledClient(deleteDidPool),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"start_phone_number": {
Description: "Starting phone number of the DID Pool range. Phone number must be in a E.164 number format. Changing the start_phone_number attribute will cause the did_pool object to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
"end_phone_number": {
Description: "Ending phone number of the DID Pool range. Phone number must be in an E.164 number format. Changing the end_phone_number attribute will cause the did_pool object to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
"description": {
Description: "DID Pool description.",
Type: schema.TypeString,
Optional: true,
},
"comments": {
Description: "Comments for the DID Pool.",
Type: schema.TypeString,
Optional: true,
},
"pool_provider": {
Description: "Provider (PURE_CLOUD | PURE_CLOUD_VOICE).",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"PURE_CLOUD", "PURE_CLOUD_VOICE"}, false),
},
},
}
}
// DataSourceDidPool registers the genesyscloud_telephony_providers_edges_did_pool data source
func DataSourceDidPool() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud DID pool. Select a DID pool by starting phone number and ending phone number",
ReadContext: provider.ReadWithPooledClient(dataSourceDidPoolRead),
Schema: map[string]*schema.Schema{
"start_phone_number": {
Description: "Starting phone number of the DID Pool range. Must be in an E.164 number format.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
"end_phone_number": {
Description: "Ending phone number of the DID Pool range.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
},
}
}
package telephony_providers_edges_did_pool
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type DidPoolStruct struct {
ResourceID string
StartPhoneNumber string
EndPhoneNumber string
Description string
Comments string
PoolProvider string
}
// DeleteDidPoolWithStartAndEndNumber deletes a did pool by start and end number. Used as a cleanup function in tests which
// utilise the did pool resource
func DeleteDidPoolWithStartAndEndNumber(ctx context.Context, startNumber, endNumber string) (*platformclientv2.APIResponse, error) {
config := platformclientv2.GetDefaultConfiguration()
proxy := getTelephonyDidPoolProxy(config)
didPoolId, _, _, err := proxy.getTelephonyDidPoolIdByStartAndEndNumber(ctx, startNumber, endNumber)
if err != nil {
return nil, err
}
return proxy.deleteTelephonyDidPool(ctx, didPoolId)
}
// GenerateDidPoolResource generates a string representation of a did pool resource based on a DidPoolStruct object
func GenerateDidPoolResource(didPool *DidPoolStruct) string {
return fmt.Sprintf(`resource "%s" "%s" {
start_phone_number = "%s"
end_phone_number = "%s"
description = %s
comments = %s
pool_provider = %s
}
`, resourceName,
didPool.ResourceID,
didPool.StartPhoneNumber,
didPool.EndPhoneNumber,
didPool.Description,
didPool.Comments,
didPool.PoolProvider)
}
package telephony_providers_edges_edge_group
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceEdgeGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
edgeGroupProxy := getEdgeGroupProxy(sdkConfig)
name := d.Get("name").(string)
managed := d.Get("managed").(bool)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
edgeGroup, retryable, resp, getErr := edgeGroupProxy.getEdgeGroupByName(ctx, name, managed)
if getErr != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting edge group %s | error: %s", name, getErr), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No edge group found with name %s", name), resp))
}
d.SetId(edgeGroup)
return nil
})
}
package telephony_providers_edges_edge_group
import (
"context"
"fmt"
"log"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *edgeGroupProxy
type getEdgeGroupByIdFunc func(ctx context.Context, p *edgeGroupProxy, edgeGroupId string) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error)
type deleteEdgeGroupFunc func(ctx context.Context, p *edgeGroupProxy, edgeGroupId string) (*platformclientv2.APIResponse, error)
type updateEdgeGroupFunc func(ctx context.Context, p *edgeGroupProxy, edgeGroupId string, body platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error)
type createEdgeGroupFunc func(ctx context.Context, p *edgeGroupProxy, body platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error)
type getAllEdgeGroupsFunc func(ctx context.Context, p *edgeGroupProxy, edgeGroupName string, managed bool) (*[]platformclientv2.Edgegroup, *platformclientv2.APIResponse, error)
type getEdgeGroupByNameFunc func(ctx context.Context, p *edgeGroupProxy, edgeGroupName string, managed bool) (string, bool, *platformclientv2.APIResponse, error)
type edgeGroupProxy struct {
clientConfig *platformclientv2.Configuration
edgesApi *platformclientv2.TelephonyProvidersEdgeApi
getEdgeGroupByIdAttr getEdgeGroupByIdFunc
deleteEdgeGroupAttr deleteEdgeGroupFunc
updateEdgeGroupAttr updateEdgeGroupFunc
createEdgeGroupAttr createEdgeGroupFunc
getAllEdgeGroupsAttr getAllEdgeGroupsFunc
getEdgeGroupByNameAttr getEdgeGroupByNameFunc
}
func newEdgeGroupProxy(clientConfig *platformclientv2.Configuration) *edgeGroupProxy {
edgesApi := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
return &edgeGroupProxy{
clientConfig: clientConfig,
edgesApi: edgesApi,
getEdgeGroupByIdAttr: getEdgeGroupByIdFn,
deleteEdgeGroupAttr: deleteEdgeGroupFn,
updateEdgeGroupAttr: updateEdgeGroupFn,
createEdgeGroupAttr: createEdgeGroupFn,
getAllEdgeGroupsAttr: getAllEdgeGroupsFn,
getEdgeGroupByNameAttr: getEdgeGroupByNameFn,
}
}
func getEdgeGroupProxy(clientConfig *platformclientv2.Configuration) *edgeGroupProxy {
if internalProxy == nil {
internalProxy = newEdgeGroupProxy(clientConfig)
}
return internalProxy
}
func (p *edgeGroupProxy) getEdgeGroupById(ctx context.Context, edgeGroupId string) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.getEdgeGroupByIdAttr(ctx, p, edgeGroupId)
}
func (p *edgeGroupProxy) deleteEdgeGroup(ctx context.Context, edgeGroupId string) (*platformclientv2.APIResponse, error) {
return p.deleteEdgeGroupAttr(ctx, p, edgeGroupId)
}
func (p *edgeGroupProxy) updateEdgeGroup(ctx context.Context, edgeGroupId string, body platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.updateEdgeGroupAttr(ctx, p, edgeGroupId, body)
}
func (p *edgeGroupProxy) createEdgeGroup(ctx context.Context, body platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.createEdgeGroupAttr(ctx, p, body)
}
func (p *edgeGroupProxy) getAllEdgeGroups(ctx context.Context, edgeGroupName string, managed bool) (*[]platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.getAllEdgeGroupsAttr(ctx, p, edgeGroupName, managed)
}
func (p *edgeGroupProxy) getEdgeGroupByName(ctx context.Context, edgeGroupName string, managed bool) (string, bool, *platformclientv2.APIResponse, error) {
return p.getEdgeGroupByNameAttr(ctx, p, edgeGroupName, managed)
}
func getEdgeGroupByNameFn(ctx context.Context, p *edgeGroupProxy, edgeGroupName string, managed bool) (string, bool, *platformclientv2.APIResponse, error) {
var targetEdgeGroup platformclientv2.Edgegroup
edgeGroups, resp, err := getAllEdgeGroupsFn(ctx, p, edgeGroupName, managed)
if err != nil {
return "", true, resp, fmt.Errorf("Error searching Edge Group By Name %s: %s", edgeGroupName, err)
}
for _, edgeGroup := range *edgeGroups {
if *edgeGroup.Name == edgeGroupName {
log.Printf("Retrieved Edge Group id %s by name %s", *edgeGroup.Id, edgeGroupName)
targetEdgeGroup = edgeGroup
return *targetEdgeGroup.Id, false, resp, nil
}
}
return "", true, resp, fmt.Errorf("Unable to find EdgeGroup with name %s", edgeGroupName)
}
func getEdgeGroupByIdFn(ctx context.Context, p *edgeGroupProxy, edgeGroupId string) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
edgeGroup, resp, err := p.edgesApi.GetTelephonyProvidersEdgesEdgegroup(edgeGroupId, nil)
if err != nil {
return nil, resp, err
}
return edgeGroup, resp, nil
}
func deleteEdgeGroupFn(ctx context.Context, p *edgeGroupProxy, edgeGroupId string) (*platformclientv2.APIResponse, error) {
resp, err := p.edgesApi.DeleteTelephonyProvidersEdgesEdgegroup(edgeGroupId)
return resp, err
}
func updateEdgeGroupFn(ctx context.Context, p *edgeGroupProxy, edgeGroupId string, body platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
edgeGroup, resp, err := p.edgesApi.PutTelephonyProvidersEdgesEdgegroup(edgeGroupId, body)
if err != nil {
return nil, resp, err
}
return edgeGroup, resp, nil
}
func createEdgeGroupFn(ctx context.Context, p *edgeGroupProxy, body platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
edgeGroup, resp, err := p.edgesApi.PostTelephonyProvidersEdgesEdgegroups(body)
if err != nil {
return nil, resp, err
}
return edgeGroup, resp, nil
}
func getAllEdgeGroupsFn(ctx context.Context, p *edgeGroupProxy, edgeGroupName string, managed bool) (*[]platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
const pageSize = 100
var allEdgeGroups []platformclientv2.Edgegroup
var pageNum = 1
edgeGroups, resp, err := p.edgesApi.GetTelephonyProvidersEdgesEdgegroups(pageSize, pageNum, edgeGroupName, "", managed)
if err != nil {
return nil, resp, err
}
if edgeGroups.Entities != nil && len(*edgeGroups.Entities) > 0 {
for _, edgeGroup := range *edgeGroups.Entities {
if edgeGroup.State != nil && *edgeGroup.State != "deleted" {
allEdgeGroups = append(allEdgeGroups, edgeGroup)
}
}
}
for pageNum := 2; pageNum <= *edgeGroups.PageCount; pageNum++ {
edgeGroups, resp, err := p.edgesApi.GetTelephonyProvidersEdgesEdgegroups(pageSize, pageNum, edgeGroupName, "", managed)
if err != nil {
return nil, resp, err
}
if edgeGroups.Entities == nil || len(*edgeGroups.Entities) == 0 {
break
}
for _, edgeGroup := range *edgeGroups.Entities {
if edgeGroup.State != nil && *edgeGroup.State != "deleted" {
allEdgeGroups = append(allEdgeGroups, edgeGroup)
}
}
}
return &allEdgeGroups, resp, nil
}
package telephony_providers_edges_edge_group
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const (
resourceName = "genesyscloud_telephony_providers_edges_edge_group"
)
func ResourceEdgeGroup() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud Edge Group. NOTE: This resource is being kept here for backwards compatibility with older Genesys Cloud Organization. You may get an error if you try to create an edge group with a Genesys Cloud Organization created in 2022 or later.`,
CreateContext: provider.CreateWithPooledClient(createEdgeGroup),
ReadContext: provider.ReadWithPooledClient(readEdgeGroup),
UpdateContext: provider.UpdateWithPooledClient(updateEdgeGroup),
DeleteContext: provider.DeleteWithPooledClient(deleteEdgeGroup),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "The resource's description.",
Type: schema.TypeString,
Optional: true,
},
"state": {
Description: "Indicates if the resource is active, inactive, or deleted.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"managed": {
Description: "Is this edge group being managed remotely.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"hybrid": {
Description: "Is this edge group hybrid.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"phone_trunk_base_ids": {
Description: "A list of trunk base settings IDs of trunkType \"PHONE\" to inherit to edge logical interface for phone communication.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
func DataSourceEdgeGroup() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Edge Group. Select an edge group by name",
ReadContext: provider.ReadWithPooledClient(dataSourceEdgeGroupRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Edge Group name.",
Type: schema.TypeString,
Required: true,
},
"managed": {
Description: "Return entities that are managed by Genesys Cloud.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource("genesyscloud_telephony_providers_edges_edge_group", DataSourceEdgeGroup())
l.RegisterResource("genesyscloud_telephony_providers_edges_edge_group", ResourceEdgeGroup())
l.RegisterExporter("genesyscloud_telephony_providers_edges_edge_group", EdgeGroupExporter())
}
func EdgeGroupExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllEdgeGroups),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"phone_trunk_base_ids": {RefType: "genesyscloud_telephony_providers_edges_trunkbasesettings"},
},
}
}
package telephony_providers_edges_edge_group
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func createEdgeGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
managed := d.Get("managed").(bool)
hybrid := d.Get("hybrid").(bool)
edgeGroup := &platformclientv2.Edgegroup{
Name: &name,
Managed: &managed,
Hybrid: &hybrid,
PhoneTrunkBases: buildSdkTrunkBases(d),
}
if description != "" {
edgeGroup.Description = &description
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgeGroupProxy := getEdgeGroupProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Creating edge group %s", name)
edgeGroup, resp, err := edgeGroupProxy.createEdgeGroup(ctx, *edgeGroup)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create edge group %s error: %s", name, err), resp)
}
d.SetId(*edgeGroup.Id)
log.Printf("Created edge group %s", *edgeGroup.Id)
return resp, nil
})
if diagErr != nil {
return diagErr
}
return readEdgeGroup(ctx, d, meta)
}
func updateEdgeGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
managed := d.Get("managed").(bool)
hybrid := d.Get("hybrid").(bool)
id := d.Id()
edgeGroup := &platformclientv2.Edgegroup{
Id: &id,
Name: &name,
Managed: &managed,
Hybrid: &hybrid,
PhoneTrunkBases: buildSdkTrunkBases(d),
}
if description != "" {
edgeGroup.Description = &description
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgeGroupProxy := getEdgeGroupProxy(sdkConfig)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
edgeGroupFromApi, resp, getErr := edgeGroupProxy.getEdgeGroupById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("The edge group does not exist %s error: %s", d.Id(), getErr), resp)
}
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read edge group %s error: %s", d.Id(), getErr), resp)
}
edgeGroup.Version = edgeGroupFromApi.Version
log.Printf("Updating edge group %s", name)
_, resp, putErr := edgeGroupProxy.updateEdgeGroup(ctx, d.Id(), *edgeGroup)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update edge group %s error: %s", name, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Updated edge group %s", *edgeGroup.Id)
return readEdgeGroup(ctx, d, meta)
}
func deleteEdgeGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgeGroupProxy := getEdgeGroupProxy(sdkConfig)
log.Printf("Deleting edge group")
resp, err := edgeGroupProxy.deleteEdgeGroup(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete edge group %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
edgeGroup, resp, err := edgeGroupProxy.getEdgeGroupById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Edge group deleted
log.Printf("Deleted Edge group %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Edge group %s | error: %s", d.Id(), err), resp))
}
if edgeGroup.State != nil && *edgeGroup.State == "deleted" {
// Edge group deleted
log.Printf("Deleted Edge group %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Edge group %s still exists", d.Id()), resp))
})
}
func readEdgeGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgeGroupProxy := getEdgeGroupProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceEdgeGroup(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading edge group %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
edgeGroup, resp, getErr := edgeGroupProxy.getEdgeGroupById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read edge group %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read edge group %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *edgeGroup.Name)
d.Set("state", *edgeGroup.State)
if edgeGroup.Description != nil {
d.Set("description", *edgeGroup.Description)
}
if edgeGroup.Managed != nil {
d.Set("managed", *edgeGroup.Managed)
}
if edgeGroup.Hybrid != nil {
d.Set("hybrid", *edgeGroup.Hybrid)
}
d.Set("phone_trunk_base_ids", nil)
if edgeGroup.PhoneTrunkBases != nil {
d.Set("phone_trunk_base_ids", flattenPhoneTrunkBases(*edgeGroup.PhoneTrunkBases))
}
log.Printf("Read edge group %s %s", d.Id(), *edgeGroup.Name)
return cc.CheckState(d)
})
}
func getAllEdgeGroups(ctx context.Context, sdkConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
edgeGroupProxy := getEdgeGroupProxy(sdkConfig)
edgeGroups, resp, err := edgeGroupProxy.getAllEdgeGroups(ctx, "", false)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get edge groups error: %s", err), resp)
}
if edgeGroups != nil {
for _, edgeGroup := range *edgeGroups {
resources[*edgeGroup.Id] = &resourceExporter.ResourceMeta{Name: *edgeGroup.Name}
}
}
return resources, nil
}
package telephony_providers_edges_edge_group
import (
"fmt"
"strings"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func buildSdkTrunkBases(d *schema.ResourceData) *[]platformclientv2.Trunkbase {
returnValue := make([]platformclientv2.Trunkbase, 0)
if ids, ok := d.GetOk("phone_trunk_base_ids"); ok {
phoneTrunkBaseIds := lists.SetToStringList(ids.(*schema.Set))
for _, trunkBaseId := range *phoneTrunkBaseIds {
id := trunkBaseId
returnValue = append(returnValue, platformclientv2.Trunkbase{
Id: &id,
})
}
}
return &returnValue
}
func GenerateEdgeGroupResourceWithCustomAttrs(
edgeGroupRes,
name,
description string,
managed,
hybrid bool,
otherAttrs ...string) string {
return fmt.Sprintf(`resource "genesyscloud_telephony_providers_edges_edge_group" "%s" {
name = "%s"
description = "%s"
managed = "%v"
hybrid = "%v"
%s
}
`, edgeGroupRes, name, description, managed, hybrid, strings.Join(otherAttrs, "\n"))
}
func GeneratePhoneTrunkBaseIds(userIDs ...string) string {
return fmt.Sprintf(`phone_trunk_base_ids = [%s]
`, strings.Join(userIDs, ","))
}
func flattenPhoneTrunkBases(trunkBases []platformclientv2.Trunkbase) *schema.Set {
interfaceList := make([]interface{}, len(trunkBases))
for i, v := range trunkBases {
interfaceList[i] = *v.Id
}
return schema.NewSet(schema.HashString, interfaceList)
}
package telephony_providers_edges_extension_pool
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceExtensionPoolRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
extensionPoolProxy := getExtensionPoolProxy(sdkConfig)
extensionPoolStartPhoneNumber := d.Get("start_number").(string)
extensionPoolEndPhoneNumber := d.Get("end_number").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
extensionPools, resp, getErr := extensionPoolProxy.getAllExtensionPools(ctx)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting list of extension pools: %s", getErr), resp))
}
if extensionPools == nil || len(*extensionPools) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no extension pools found with start phone number: %s and end phone number: %s", extensionPoolStartPhoneNumber, extensionPoolEndPhoneNumber), resp))
}
for _, extensionPool := range *extensionPools {
if extensionPool.StartNumber != nil && *extensionPool.StartNumber == extensionPoolStartPhoneNumber &&
extensionPool.EndNumber != nil && *extensionPool.EndNumber == extensionPoolEndPhoneNumber &&
extensionPool.State != nil && *extensionPool.State != "deleted" {
d.SetId(*extensionPool.Id)
}
}
return nil
})
}
package telephony_providers_edges_extension_pool
import (
"context"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *extensionPoolProxy
type getExtensionPoolFunc func(ctxctx context.Context, p *extensionPoolProxy, extensionPoolId string) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error)
type deleteExtensionPoolFunc func(ctx context.Context, p *extensionPoolProxy, extensionPoolId string) (*platformclientv2.APIResponse, error)
type updateExtensionPoolFunc func(ctx context.Context, p *extensionPoolProxy, extensionPoolId string, body platformclientv2.Extensionpool) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error)
type createExtensionPoolFunc func(ctx context.Context, p *extensionPoolProxy, body platformclientv2.Extensionpool) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error)
type getAllExtensionPoolsFunc func(ctx context.Context, p *extensionPoolProxy) (*[]platformclientv2.Extensionpool, *platformclientv2.APIResponse, error)
// ExtensionPoolProxy represents the interface required to access the extension pool custom resource
type extensionPoolProxy struct {
clientConfig *platformclientv2.Configuration
edgesApi *platformclientv2.TelephonyProvidersEdgeApi
getExtensionPoolAttr getExtensionPoolFunc
deleteExtensionPoolAttr deleteExtensionPoolFunc
updateExtensionPoolAttr updateExtensionPoolFunc
createExtensionPoolAttr createExtensionPoolFunc
getAllExtensionPoolsAttr getAllExtensionPoolsFunc
}
func newExtensionPoolProxy(clientConfig *platformclientv2.Configuration) *extensionPoolProxy {
edgesApi := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
return &extensionPoolProxy{
clientConfig: clientConfig,
edgesApi: edgesApi,
getExtensionPoolAttr: getExtensionPoolFn,
deleteExtensionPoolAttr: deleteExtensionPoolFn,
updateExtensionPoolAttr: updateExtensionPoolFn,
createExtensionPoolAttr: createExtensionPoolFn,
getAllExtensionPoolsAttr: getAllExtensionPoolsFn,
}
}
func getExtensionPoolProxy(clientConfig *platformclientv2.Configuration) *extensionPoolProxy {
if internalProxy == nil {
internalProxy = newExtensionPoolProxy(clientConfig)
}
return internalProxy
}
func (p *extensionPoolProxy) getExtensionPool(ctx context.Context, extensionPoolId string) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
return p.getExtensionPoolAttr(ctx, p, extensionPoolId)
}
func (p *extensionPoolProxy) deleteExtensionPool(ctx context.Context, extensionPoolId string) (*platformclientv2.APIResponse, error) {
return p.deleteExtensionPoolAttr(ctx, p, extensionPoolId)
}
func (p *extensionPoolProxy) updateExtensionPool(ctx context.Context, extensionPoolId string, body platformclientv2.Extensionpool) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
return p.updateExtensionPoolAttr(ctx, p, extensionPoolId, body)
}
func (p *extensionPoolProxy) createExtensionPool(ctx context.Context, body platformclientv2.Extensionpool) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
return p.createExtensionPoolAttr(ctx, p, body)
}
func (p *extensionPoolProxy) getAllExtensionPools(ctx context.Context) (*[]platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
return p.getAllExtensionPoolsAttr(ctx, p)
}
func getExtensionPoolFn(ctx context.Context, p *extensionPoolProxy, extensionPoolId string) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
extensionPool, resp, err := p.edgesApi.GetTelephonyProvidersEdgesExtensionpool(extensionPoolId)
if err != nil {
return nil, resp, err
}
return extensionPool, resp, nil
}
func deleteExtensionPoolFn(ctx context.Context, p *extensionPoolProxy, extensionPoolId string) (*platformclientv2.APIResponse, error) {
resp, err := p.edgesApi.DeleteTelephonyProvidersEdgesExtensionpool(extensionPoolId)
return resp, err
}
func updateExtensionPoolFn(ctx context.Context, p *extensionPoolProxy, extensionPoolId string, body platformclientv2.Extensionpool) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
extensionPool, resp, err := p.edgesApi.PutTelephonyProvidersEdgesExtensionpool(extensionPoolId, body)
if err != nil {
return nil, resp, err
}
return extensionPool, resp, nil
}
func createExtensionPoolFn(ctx context.Context, p *extensionPoolProxy, body platformclientv2.Extensionpool) (*platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
extensionPool, resp, err := p.edgesApi.PostTelephonyProvidersEdgesExtensionpools(body)
if err != nil {
return nil, resp, err
}
return extensionPool, resp, nil
}
func getAllExtensionPoolsFn(ctx context.Context, p *extensionPoolProxy) (*[]platformclientv2.Extensionpool, *platformclientv2.APIResponse, error) {
const pageSize = 100
var (
allExtensionPools []platformclientv2.Extensionpool
pageNum = 1
)
//Checking First Page
extensionPools, resp, err := p.edgesApi.GetTelephonyProvidersEdgesExtensionpools(pageSize, pageNum, "", "")
if err != nil {
return nil, resp, err
}
if extensionPools.Entities != nil && len(*extensionPools.Entities) > 0 {
for _, extensionPool := range *extensionPools.Entities {
if extensionPool.State != nil && *extensionPool.State != "deleted" {
allExtensionPools = append(allExtensionPools, extensionPool)
}
}
}
if *extensionPools.PageCount < 2 {
return &allExtensionPools, resp, nil
}
for pageNum := 2; pageNum <= *extensionPools.PageCount; pageNum++ {
extensionPools, resp, err := p.edgesApi.GetTelephonyProvidersEdgesExtensionpools(pageSize, pageNum, "", "")
if err != nil {
return nil, resp, err
}
if extensionPools.Entities == nil || len(*extensionPools.Entities) == 0 {
break
}
for _, extensionPool := range *extensionPools.Entities {
if extensionPool.State != nil && *extensionPool.State != "deleted" {
allExtensionPools = append(allExtensionPools, extensionPool)
}
}
}
return &allExtensionPools, resp, nil
}
package telephony_providers_edges_extension_pool
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
const (
resourceName = "genesyscloud_telephony_providers_edges_extension_pool"
)
func ResourceTelephonyExtensionPool() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Extension Pool",
CreateContext: provider.CreateWithPooledClient(createExtensionPool),
ReadContext: provider.ReadWithPooledClient(readExtensionPool),
UpdateContext: provider.UpdateWithPooledClient(updateExtensionPool),
DeleteContext: provider.DeleteWithPooledClient(deleteExtensionPool),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"start_number": {
Description: "Starting phone number of the Extension Pool range. Changing the start_number attribute will cause the extension object to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: gcloud.ValidateExtensionPool,
},
"end_number": {
Description: "Ending phone number of the Extension Pool range. Changing the end_number attribute will cause the extension object to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: gcloud.ValidateExtensionPool,
},
"description": {
Description: "Extension Pool description.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
func DataSourceExtensionPool() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Extension pool. Select an Extension pool by starting number and ending number",
ReadContext: provider.ReadWithPooledClient(dataSourceExtensionPoolRead),
Schema: map[string]*schema.Schema{
"start_number": {
Description: "Starting number of the Extension Pool range.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateExtensionPool,
},
"end_number": {
Description: "Ending number of the Extension Pool range.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateExtensionPool,
},
},
}
}
func TelephonyExtensionPoolExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllExtensionPools),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references
}
}
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource("genesyscloud_telephony_providers_edges_extension_pool", DataSourceExtensionPool())
l.RegisterResource("genesyscloud_telephony_providers_edges_extension_pool", ResourceTelephonyExtensionPool())
l.RegisterExporter("genesyscloud_telephony_providers_edges_extension_pool", TelephonyExtensionPoolExporter())
}
package telephony_providers_edges_extension_pool
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllExtensionPools(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
extensionPoolProxy := getExtensionPoolProxy(clientConfig)
extensionPools, resp, err := extensionPoolProxy.getAllExtensionPools(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get extension pools error: %s", err), resp)
}
if extensionPools != nil {
for _, extensionPool := range *extensionPools {
resources[*extensionPool.Id] = &resourceExporter.ResourceMeta{Name: *extensionPool.StartNumber}
}
}
return resources, nil
}
func createExtensionPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
startNumber := d.Get("start_number").(string)
endNumber := d.Get("end_number").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
extensionPoolProxy := getExtensionPoolProxy(sdkConfig)
log.Printf("Creating Extension pool %s", startNumber)
extensionPool, resp, err := extensionPoolProxy.createExtensionPool(ctx, platformclientv2.Extensionpool{
StartNumber: &startNumber,
EndNumber: &endNumber,
Description: &description,
})
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create extension pool %s error: %s", startNumber, err), resp)
}
d.SetId(*extensionPool.Id)
log.Printf("Created Extension pool %s %s", startNumber, *extensionPool.Id)
return readExtensionPool(ctx, d, meta)
}
func readExtensionPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
extensionPoolProxy := getExtensionPoolProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTelephonyExtensionPool(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading Extension pool %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
extensionPool, resp, getErr := extensionPoolProxy.getExtensionPool(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Extension pool %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Extension pool %s | error: %s", d.Id(), getErr), resp))
}
if extensionPool.State != nil && *extensionPool.State == "deleted" {
d.SetId("")
return nil
}
d.Set("start_number", *extensionPool.StartNumber)
d.Set("end_number", *extensionPool.EndNumber)
if extensionPool.Description != nil {
d.Set("description", *extensionPool.Description)
} else {
d.Set("description", nil)
}
log.Printf("Read Extension pool %s %s", d.Id(), *extensionPool.StartNumber)
return cc.CheckState(d)
})
}
func updateExtensionPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
startNumber := d.Get("start_number").(string)
endNumber := d.Get("end_number").(string)
description := d.Get("description").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
extensionPoolProxy := getExtensionPoolProxy(sdkConfig)
extensionPoolBody := platformclientv2.Extensionpool{
StartNumber: &startNumber,
EndNumber: &endNumber,
Description: &description,
}
log.Printf("Updating Extension pool %s", d.Id())
if _, resp, err := extensionPoolProxy.updateExtensionPool(ctx, d.Id(), extensionPoolBody); err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update extension pool %s error: %s", startNumber, err), resp)
}
log.Printf("Updated Extension pool %s", d.Id())
return readExtensionPool(ctx, d, meta)
}
func deleteExtensionPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
startNumber := d.Get("start_number").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
extensionPoolProxy := getExtensionPoolProxy(sdkConfig)
log.Printf("Deleting Extension pool with starting number %s", startNumber)
if resp, err := extensionPoolProxy.deleteExtensionPool(ctx, d.Id()); err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete extension pool %s error: %s", startNumber, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
extensionPool, resp, err := extensionPoolProxy.getExtensionPool(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Extension pool deleted
log.Printf("Deleted Extension pool %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Extension pool %s | error: %s", d.Id(), err), resp))
}
if extensionPool.State != nil && *extensionPool.State == "deleted" {
// Extension pool deleted
log.Printf("Deleted Extension pool %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("extension pool %s still exists", d.Id()), resp))
})
}
package telephony_providers_edges_linebasesettings
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceLineBaseSettingsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 50
lineBaseSettings, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesLinebasesettings(pageNum, pageSize, "", "", nil)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting line base settings %s | error: %s", name, getErr), resp))
}
if lineBaseSettings.Entities == nil || len(*lineBaseSettings.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No lineBaseSettings found with name %s", name), resp))
}
for _, lineBaseSetting := range *lineBaseSettings.Entities {
if lineBaseSetting.Name != nil && *lineBaseSetting.Name == name &&
lineBaseSetting.State != nil && *lineBaseSetting.State != "deleted" {
d.SetId(*lineBaseSetting.Id)
return nil
}
}
}
})
}
package telephony_providers_edges_linebasesettings
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_telephony_providers_edges_linebasesettings"
func DataSourceLineBaseSettings() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Line Base Settings. Select a line base settings by name",
ReadContext: provider.ReadWithPooledClient(dataSourceLineBaseSettingsRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Line Base Settings name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource("genesyscloud_telephony_providers_edges_linebasesettings", DataSourceLineBaseSettings())
}
package telephony_providers_edges_phone
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourcePhoneRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
pp := getPhoneProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
phone, retryable, resp, err := pp.getPhoneByName(ctx, name)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting phone %s | error: %s", name, err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no phone found with name %s", name), resp))
}
d.SetId(*phone.Id)
return nil
})
}
package telephony_providers_edges_phone
import (
"context"
"fmt"
"log"
"net/http"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_telephony_providers_edges_phone_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *phoneProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllPhonesFunc func(ctx context.Context, p *phoneProxy) (*[]platformclientv2.Phone, *platformclientv2.APIResponse, error)
type createPhoneFunc func(ctx context.Context, p *phoneProxy, phoneConfig *platformclientv2.Phone) (*platformclientv2.Phone, *platformclientv2.APIResponse, error)
type getPhoneByIdFunc func(ctx context.Context, p *phoneProxy, phoneId string) (*platformclientv2.Phone, *platformclientv2.APIResponse, error)
type getPhoneByNameFunc func(ctx context.Context, p *phoneProxy, phoneName string) (phone *platformclientv2.Phone, retryable bool, resp *platformclientv2.APIResponse, err error)
type updatePhoneFunc func(ctx context.Context, p *phoneProxy, phoneId string, phoneConfig *platformclientv2.Phone) (*platformclientv2.Phone, *platformclientv2.APIResponse, error)
type deletePhoneFunc func(ctx context.Context, p *phoneProxy, phoneId string) (response *platformclientv2.APIResponse, err error)
type getPhoneBaseSettingFunc func(ctx context.Context, p *phoneProxy, phoneBaseSettingsId string) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error)
type getStationOfUserFunc func(ctx context.Context, p *phoneProxy, userId string) (station *platformclientv2.Station, retryable bool, resp *platformclientv2.APIResponse, err error)
type unassignUserFromStationFunc func(ctx context.Context, p *phoneProxy, stationId string) (*platformclientv2.APIResponse, error)
type assignUserToStationFunc func(ctx context.Context, p *phoneProxy, userId string, stationId string) (*platformclientv2.APIResponse, error)
type assignStationAsDefaultFunc func(ctx context.Context, p *phoneProxy, userId string, stationId string) (*platformclientv2.APIResponse, error)
// phoneProxy contains all of the methods that call genesys cloud APIs.
type phoneProxy struct {
clientConfig *platformclientv2.Configuration
edgesApi *platformclientv2.TelephonyProvidersEdgeApi
stationsApi *platformclientv2.StationsApi
usersApi *platformclientv2.UsersApi
getAllPhonesAttr getAllPhonesFunc
createPhoneAttr createPhoneFunc
getPhoneByIdAttr getPhoneByIdFunc
getPhoneByNameAttr getPhoneByNameFunc
updatePhoneAttr updatePhoneFunc
deletePhoneAttr deletePhoneFunc
getPhoneBaseSettingAttr getPhoneBaseSettingFunc
getStationOfUserAttr getStationOfUserFunc
unassignUserFromStationAttr unassignUserFromStationFunc
assignUserToStationAttr assignUserToStationFunc
assignStationAsDefaultAttr assignStationAsDefaultFunc
}
// newPhoneProxy initializes the Phone proxy with all of the data needed to communicate with Genesys Cloud
func newPhoneProxy(clientConfig *platformclientv2.Configuration) *phoneProxy {
edgesApi := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
stationsApi := platformclientv2.NewStationsApiWithConfig(clientConfig)
usersApi := platformclientv2.NewUsersApiWithConfig(clientConfig)
return &phoneProxy{
clientConfig: clientConfig,
edgesApi: edgesApi,
stationsApi: stationsApi,
usersApi: usersApi,
getAllPhonesAttr: getAllPhonesFn,
createPhoneAttr: createPhoneFn,
getPhoneByIdAttr: getPhoneByIdFn,
getPhoneByNameAttr: getPhoneByNameFn,
updatePhoneAttr: updatePhoneFn,
deletePhoneAttr: deletePhoneFn,
getPhoneBaseSettingAttr: getPhoneBaseSettingFn,
getStationOfUserAttr: getStationOfUserFn,
unassignUserFromStationAttr: unassignUserFromStationFn,
assignUserToStationAttr: assignUserToStationFn,
assignStationAsDefaultAttr: assignStationAsDefaultFn,
}
}
// getPhoneProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getPhoneProxy(clientConfig *platformclientv2.Configuration) *phoneProxy {
if internalProxy == nil {
internalProxy = newPhoneProxy(clientConfig)
}
return internalProxy
}
// getAllPhones retrieves all Genesys Cloud Phones
func (p *phoneProxy) getAllPhones(ctx context.Context) (*[]platformclientv2.Phone, *platformclientv2.APIResponse, error) {
return p.getAllPhonesAttr(ctx, p)
}
// createPhone creates a Genesys Cloud Phone
func (p *phoneProxy) createPhone(ctx context.Context, phoneConfig *platformclientv2.Phone) (*platformclientv2.Phone, *platformclientv2.APIResponse, error) {
return p.createPhoneAttr(ctx, p, phoneConfig)
}
// getPhoneById retrieves a Genesys Cloud Phone by id
func (p *phoneProxy) getPhoneById(ctx context.Context, phoneId string) (*platformclientv2.Phone, *platformclientv2.APIResponse, error) {
return p.getPhoneByIdAttr(ctx, p, phoneId)
}
// getPhoneByName retrieves a Genesys Cloud Phone by name
func (p *phoneProxy) getPhoneByName(ctx context.Context, phoneName string) (phone *platformclientv2.Phone, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getPhoneByNameAttr(ctx, p, phoneName)
}
// updatePhone updates a Genesys Cloud Phone
func (p *phoneProxy) updatePhone(ctx context.Context, phoneId string, phoneConfig *platformclientv2.Phone) (*platformclientv2.Phone, *platformclientv2.APIResponse, error) {
return p.updatePhoneAttr(ctx, p, phoneId, phoneConfig)
}
// deletePhone deletes a Genesys Cloud Phone
func (p *phoneProxy) deletePhone(ctx context.Context, phoneId string) (response *platformclientv2.APIResponse, err error) {
return p.deletePhoneAttr(ctx, p, phoneId)
}
// getPhoneBaseSetting retrieves a Genesys Cloud Phone Base Setting
func (p *phoneProxy) getPhoneBaseSetting(ctx context.Context, phoneBaseSettingsId string) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
return p.getPhoneBaseSettingAttr(ctx, p, phoneBaseSettingsId)
}
// getStationOfUser retrieves the station of a user
func (p *phoneProxy) getStationOfUser(ctx context.Context, userId string) (*platformclientv2.Station, bool, *platformclientv2.APIResponse, error) {
return p.getStationOfUserAttr(ctx, p, userId)
}
// unassignUserFromStation unassigns a user from the station
func (p *phoneProxy) unassignUserFromStation(ctx context.Context, stationId string) (*platformclientv2.APIResponse, error) {
return p.unassignUserFromStationAttr(ctx, p, stationId)
}
// assignUserToStation assigns a user to the station
func (p *phoneProxy) assignUserToStation(ctx context.Context, userId string, stationId string) (*platformclientv2.APIResponse, error) {
return p.assignUserToStationAttr(ctx, p, userId, stationId)
}
// assignStationAsDefault assigns a station as the default
func (p *phoneProxy) assignStationAsDefault(ctx context.Context, userId string, stationId string) (*platformclientv2.APIResponse, error) {
return p.assignStationAsDefaultAttr(ctx, p, userId, stationId)
}
// getAllPhonesFn is an implementation function for retrieving all Genesys Cloud Phones
func getAllPhonesFn(ctx context.Context, p *phoneProxy) (*[]platformclientv2.Phone, *platformclientv2.APIResponse, error) {
log.Printf("Entering the getAllPhonesFn method to retrieve all of the phone ids for export")
var allPhones []platformclientv2.Phone
const pageSize = 100
const sortBy = "id"
phones, response, err := p.edgesApi.GetTelephonyProvidersEdgesPhones(1, pageSize, sortBy, "", "", "", "", "", "", "", "", "", "", "", "", nil, nil)
if err != nil || (response != nil && response.StatusCode != http.StatusOK) {
log.Printf("getAllPhonesFn:: error encountered while trying to get first page of phone data #%v statusCode: %d", err, response.StatusCode)
return nil, response, err
}
if phones != nil && phones.Entities != nil {
log.Printf("getAllPhonesFn::: Retrieved page 1 of %d pages of phone data. Total number of phone records is %d", phones.PageCount, phones.Total)
for _, phone := range *phones.Entities {
if phone.State != nil && *phone.State != "deleted" {
allPhones = append(allPhones, phone)
}
}
} else {
log.Printf("getAllPhonesFn:: No phone records were retrieved (phone or on the first call to p.edgesApi.GetTelephonyProvidersEdgesPhones.")
phones := make([]platformclientv2.Phone, 0)
return &phones, response, nil
}
for pageNum := 2; pageNum <= *phones.PageCount; pageNum++ {
phones, response, err := p.edgesApi.GetTelephonyProvidersEdgesPhones(pageNum, pageSize, sortBy, "", "", "", "", "", "", "", "", "", "", "", "", nil, nil)
if err != nil || (response != nil && response.StatusCode != http.StatusOK) {
return nil, response, err
}
if phones.Entities == nil || len(*phones.Entities) == 0 {
break
}
for _, phone := range *phones.Entities {
if phone.State != nil && *phone.State != "deleted" {
allPhones = append(allPhones, phone)
}
}
}
log.Printf("getAllPhonesFn:: Listing all of the non-deleted phone ids and names that we actually retrieved")
for _, phone := range allPhones {
log.Printf("getAllPhonesFn:: Retrieved phone id %s with phone name: %s\n", *phone.Id, *phone.Name)
}
return &allPhones, response, nil
}
// createPhoneFn is an implementation function for creating a Genesys Cloud Phone
func createPhoneFn(ctx context.Context, p *phoneProxy, phoneConfig *platformclientv2.Phone) (*platformclientv2.Phone, *platformclientv2.APIResponse, error) {
phone, resp, err := p.edgesApi.PostTelephonyProvidersEdgesPhones(*phoneConfig)
if err != nil {
return nil, resp, err
}
return phone, resp, nil
}
// getPhoneByIdFn is an implementation function for retrieving a Genesys Cloud Phone by id
func getPhoneByIdFn(ctx context.Context, p *phoneProxy, phoneId string) (*platformclientv2.Phone, *platformclientv2.APIResponse, error) {
phone, resp, err := p.edgesApi.GetTelephonyProvidersEdgesPhone(phoneId)
if err != nil {
return nil, resp, err
}
log.Printf("getPhoneByIdFn:: Successfully retrieved individual phone record id %s with phone name %s.\n", *phone.Id, *phone.Name)
return phone, resp, nil
}
// getPhoneByNameFn is an implementation function for retrieving a Genesys Cloud Phone by name
func getPhoneByNameFn(ctx context.Context, p *phoneProxy, phoneName string) (phone *platformclientv2.Phone, retryable bool, resp *platformclientv2.APIResponse, err error) {
const pageSize = 100
phones, resp, err := p.edgesApi.GetTelephonyProvidersEdgesPhones(1, pageSize, "", "", "", "", "", "", "", "", "", "", phoneName, "", "", nil, nil)
if err != nil {
return nil, false, resp, err
}
if phones.Entities == nil || len(*phones.Entities) == 0 {
return nil, true, resp, fmt.Errorf("failed to find ID of phone '%s'", phoneName)
}
for _, phone := range *phones.Entities {
if *phone.Name == phoneName {
return &phone, false, resp, nil
}
}
for pageNum := 2; pageNum <= *phones.PageCount; pageNum++ {
phones, resp, err := p.edgesApi.GetTelephonyProvidersEdgesPhones(pageNum, pageSize, "", "", "", "", "", "", "", "", "", "", phoneName, "", "", nil, nil)
if err != nil {
return nil, false, resp, err
}
if phones.Entities == nil {
return nil, true, resp, fmt.Errorf("failed to find ID of phone '%s'", phoneName)
}
for _, phone := range *phones.Entities {
if *phone.Name == phoneName {
return &phone, false, resp, nil
}
}
}
return nil, true, resp, fmt.Errorf("failed to find ID of phone '%s'", phoneName)
}
// updatePhoneFn is an implementation function for updating a Genesys Cloud Phone
func updatePhoneFn(ctx context.Context, p *phoneProxy, phoneId string, phoneConfig *platformclientv2.Phone) (*platformclientv2.Phone, *platformclientv2.APIResponse, error) {
phone, resp, err := p.edgesApi.PutTelephonyProvidersEdgesPhone(phoneId, *phoneConfig)
if err != nil {
return nil, resp, err
}
return phone, resp, err
}
// deletePhoneFn is an implementation function for deleting a Genesys Cloud Phone
func deletePhoneFn(ctx context.Context, p *phoneProxy, phoneId string) (response *platformclientv2.APIResponse, err error) {
resp, err := p.edgesApi.DeleteTelephonyProvidersEdgesPhone(phoneId)
return resp, err
}
// getPhoneBaseSettingFn is an implementation function for retrieving a Genesys Cloud Phone Base Setting
func getPhoneBaseSettingFn(ctx context.Context, p *phoneProxy, phoneBaseSettingsId string) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
phoneBase, resp, err := p.edgesApi.GetTelephonyProvidersEdgesPhonebasesetting(phoneBaseSettingsId)
if err != nil {
return nil, resp, err
}
return phoneBase, resp, nil
}
// getStationOfUserFn is an implementation function for retrieving a Genesys Cloud User Station
func getStationOfUserFn(ctx context.Context, p *phoneProxy, userId string) (station *platformclientv2.Station, retryable bool, resp *platformclientv2.APIResponse, err error) {
const pageSize = 100
const pageNum = 1
stations, resp, err := p.stationsApi.GetStations(pageSize, pageNum, "", "", "", userId, "", "")
if err != nil {
return nil, false, resp, err
}
if stations.Entities == nil || len(*stations.Entities) == 0 {
return nil, true, resp, nil
}
return &(*stations.Entities)[0], false, resp, err
}
// unassignUserFromStationFn is an implementation function for unassigning a Genesys Cloud User from a Station
func unassignUserFromStationFn(ctx context.Context, p *phoneProxy, stationId string) (*platformclientv2.APIResponse, error) {
return p.stationsApi.DeleteStationAssociateduser(stationId)
}
// assignUserToStationFn is an implementation function for assigning a Genesys Cloud User to a Station
func assignUserToStationFn(ctx context.Context, p *phoneProxy, userId string, stationId string) (*platformclientv2.APIResponse, error) {
return p.usersApi.PutUserStationAssociatedstationStationId(userId, stationId)
}
// assignStationAsDefaultFn is an implementation function for assigning a station as Default Station
func assignStationAsDefaultFn(ctx context.Context, p *phoneProxy, userId string, stationId string) (*platformclientv2.APIResponse, error) {
return p.usersApi.PutUserStationDefaultstationStationId(userId, stationId)
}
package telephony_providers_edges_phone
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllPhones(ctx context.Context, sdkConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
pp := getPhoneProxy(sdkConfig)
phones, resp, err := pp.getAllPhones(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of phones error: %s", err), resp)
}
for _, phone := range *phones {
resources[*phone.Id] = &resourceExporter.ResourceMeta{Name: *phone.Name}
}
return resources, nil
}
func createPhone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPhoneProxy(sdkConfig)
phoneConfig, err := getPhoneFromResourceData(ctx, pp, d)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to create phone %v", *phoneConfig.Name), err)
}
log.Printf("Creating phone %s", *phoneConfig.Name)
diagErr := util.RetryWhen(util.IsStatus404, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
phone, resp, err := pp.createPhone(ctx, phoneConfig)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create phone %s error: %s", *phoneConfig.Name, err), resp)
}
log.Printf("Completed call to create phone name %s with status code %d, correlation id %s", *phoneConfig.Name, resp.StatusCode, resp.CorrelationID)
d.SetId(*phone.Id)
webRtcUserId := d.Get("web_rtc_user_id")
if webRtcUserId != "" {
diagErr := assignUserToWebRtcPhone(ctx, pp, webRtcUserId.(string))
if diagErr != nil {
return resp, diagErr
}
}
log.Printf("Created phone %s", *phone.Id)
return nil, nil
})
if diagErr != nil {
return diagErr
}
return readPhone(ctx, d, meta)
}
func readPhone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPhoneProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourcePhone(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading phone %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
currentPhone, resp, getErr := pp.getPhoneById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read phone %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read phone %s | error: %s", d.Id(), getErr), resp))
}
_ = d.Set("name", *currentPhone.Name)
_ = d.Set("state", *currentPhone.State)
_ = d.Set("site_id", *currentPhone.Site.Id)
_ = d.Set("phone_base_settings_id", *currentPhone.PhoneBaseSettings.Id)
_ = d.Set("line_base_settings_id", *currentPhone.LineBaseSettings.Id)
if currentPhone.PhoneMetaBase != nil {
_ = d.Set("phone_meta_base_id", *currentPhone.PhoneMetaBase.Id)
}
if currentPhone.WebRtcUser != nil {
_ = d.Set("web_rtc_user_id", *currentPhone.WebRtcUser.Id)
}
if currentPhone.Lines != nil {
_ = d.Set("line_addresses", flattenPhoneLines(currentPhone.Lines))
}
_ = d.Set("properties", nil)
if currentPhone.Properties != nil {
properties, err := util.FlattenTelephonyProperties(currentPhone.Properties)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
_ = d.Set("properties", properties)
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "capabilities", currentPhone.Capabilities, flattenPhoneCapabilities)
log.Printf("Read phone %s %s", d.Id(), *currentPhone.Name)
return cc.CheckState(d)
})
}
func updatePhone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPhoneProxy(sdkConfig)
phoneConfig, err := getPhoneFromResourceData(ctx, pp, d)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("failed to updated phone %v", *phoneConfig.Name), err)
}
log.Printf("Updating phone %s", *phoneConfig.Name)
phone, resp, err := pp.updatePhone(ctx, d.Id(), phoneConfig)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update phone %s error: %s", *phoneConfig.Name, err), resp)
}
log.Printf("Updated phone %s", *phone.Id)
webRtcUserId := d.Get("web_rtc_user_id")
if webRtcUserId != "" {
if d.HasChange("web_rtc_user_id") {
diagErr := assignUserToWebRtcPhone(ctx, pp, webRtcUserId.(string))
if diagErr != nil {
return diagErr
}
}
}
return readPhone(ctx, d, meta)
}
func deletePhone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
pp := getPhoneProxy(sdkConfig)
log.Printf("Deleting Phone")
resp, err := pp.deletePhone(ctx, d.Id())
/*
Adding a small sleep because when a phone is deleted, the station associated with the phone and the site
objects need time to disassociate from the phone. This eventual consistency problem was discovered during
building the GCX Now project. Adding the sleep gives the platform time to settle down.
*/
time.Sleep(5 * time.Second)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete phone %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
phone, resp, err := pp.getPhoneById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Phone deleted
log.Printf("Deleted Phone %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Phone %s | error: %s", d.Id(), err), resp))
}
if phone.State != nil && *phone.State == "deleted" {
// phone deleted
log.Printf("Deleted Phone %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("phone %s still exists", d.Id()), resp))
})
}
package telephony_providers_edges_phone
import (
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
/*
resource_genesyscloud_telephony_providers_edges_phone_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the telephony_providers_edges_phone resource.
3. The datasource schema definitions for the telephony_providers_edges_phone datasource.
4. The resource exporter configuration for the telephony_providers_edges_phone exporter.
*/
const resourceName = "genesyscloud_telephony_providers_edges_phone"
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourcePhone())
l.RegisterResource(resourceName, ResourcePhone())
l.RegisterExporter(resourceName, PhoneExporter())
}
// ResourcePhone registers the genesyscloud_telephony_providers_edges_phone resource with Terraform
func ResourcePhone() *schema.Resource {
phoneCapabilities := &schema.Resource{
Schema: map[string]*schema.Schema{
"provisions": {
Description: "Provisions",
Type: schema.TypeBool,
Optional: true,
},
"registers": {
Description: "Registers",
Type: schema.TypeBool,
Optional: true,
},
"dual_registers": {
Description: "Dual Registers",
Type: schema.TypeBool,
Optional: true,
},
"hardware_id_type": {
Description: "HardwareId Type",
Type: schema.TypeString,
Optional: true,
},
"allow_reboot": {
Description: "Allow Reboot",
Type: schema.TypeBool,
Optional: true,
},
"no_rebalance": {
Description: "No Rebalance",
Type: schema.TypeBool,
Optional: true,
},
"no_cloud_provisioning": {
Description: "No Cloud Provisioning",
Type: schema.TypeBool,
Optional: true,
},
"media_codecs": {
Description: "Media Codecs",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"audio/opus", "audio/pcmu", "audio/pcma", "audio/g729", "audio/g722"}, false),
},
},
"cdm": {
Description: "CDM",
Type: schema.TypeBool,
Optional: true,
},
},
}
return &schema.Resource{
Description: "Genesys Cloud Phone",
CreateContext: provider.CreateWithPooledClient(createPhone),
ReadContext: provider.ReadWithPooledClient(readPhone),
UpdateContext: provider.UpdateWithPooledClient(updatePhone),
DeleteContext: provider.DeleteWithPooledClient(deletePhone),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"state": {
Description: "Indicates if the resource is active, inactive, or deleted. Valid values: active, inactive, deleted.",
Type: schema.TypeString,
Optional: true,
Default: "active",
ValidateFunc: validation.StringInSlice([]string{"active", "inactive", "deleted"}, false),
},
"site_id": {
Description: "The site ID associated to the phone.",
Type: schema.TypeString,
Required: true,
},
"phone_base_settings_id": {
Description: "Phone Base Settings ID.",
Type: schema.TypeString,
Required: true,
},
"line_base_settings_id": {
Description: "Line Base Settings ID.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"phone_meta_base_id": {
Description: "Phone Meta Base ID.",
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"web_rtc_user_id": {
Description: "Web RTC User ID. This is necessary when creating a Web RTC phone. This user will be assigned to the phone after it is created.",
Type: schema.TypeString,
Optional: true,
},
"line_addresses": {
Description: "Ordered list of Line DIDs for standalone phones. Each phone number must be in an E.164 phone number format.",
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString, ValidateDiagFunc: gcloud.ValidatePhoneNumber},
},
"properties": {
Description: "phone properties",
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"capabilities": {
Description: "Phone Capabilities.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: phoneCapabilities,
},
},
CustomizeDiff: util.CustomizePhonePropertiesDiff,
}
}
// PhoneExporter returns the resourceExporter object used to hold the genesyscloud_telephony_providers_edges_phone exporter's config
func PhoneExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllPhones),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"web_rtc_user_id": {RefType: "genesyscloud_user"},
"site_id": {RefType: "genesyscloud_telephony_providers_edges_site"},
"phone_base_settings_id": {RefType: "genesyscloud_telephony_providers_edges_phonebasesettings"},
},
}
}
// DataSourcePhone registers the genesyscloud_telephony_providers_edges_phone data source
func DataSourcePhone() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Phone. Select a phone by name",
ReadContext: provider.ReadWithPooledClient(dataSourcePhoneRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Phone name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
package telephony_providers_edges_phone
import (
"context"
"fmt"
"hash/fnv"
"log"
"strconv"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type PhoneConfig struct {
PhoneRes string
Name string
State string
SiteId string
PhoneBaseSettingsId string
LineAddresses []string
WebRtcUserId string
DependsOn string
}
func getPhoneFromResourceData(ctx context.Context, pp *phoneProxy, d *schema.ResourceData) (*platformclientv2.Phone, error) {
phoneConfig := &platformclientv2.Phone{
Name: platformclientv2.String(d.Get("name").(string)),
State: platformclientv2.String(d.Get("state").(string)),
Site: util.BuildSdkDomainEntityRef(d, "site_id"),
Properties: util.BuildTelephonyProperties(d),
PhoneBaseSettings: &platformclientv2.Phonebasesettings{
Id: buildSdkPhoneBaseSettings(d, "phone_base_settings_id").Id,
},
Capabilities: buildSdkCapabilities(d),
}
// Line base settings and lines
var err error
lineBaseSettingsID := d.Get("line_base_settings_id").(string)
if lineBaseSettingsID == "" {
lineBaseSettingsID, err = getLineBaseSettingsID(ctx, pp, *phoneConfig.PhoneBaseSettings.Id)
if err != nil {
return nil, fmt.Errorf("failed to get line base settings for %s: %s", *phoneConfig.Name, err)
}
}
lineBaseSettings := &platformclientv2.Domainentityref{Id: &lineBaseSettingsID}
lines, isStandalone := buildSdkLines(ctx, pp, d, lineBaseSettings)
phoneConfig.LineBaseSettings = lineBaseSettings
phoneConfig.Lines = lines
// phone meta base
phoneMetaBaseId, err := getPhoneMetaBaseId(ctx, pp, *phoneConfig.PhoneBaseSettings.Id)
if err != nil {
return nil, fmt.Errorf("failed to get phone meta base for %s: %s", *phoneConfig.Name, err)
}
phoneMetaBase := &platformclientv2.Domainentityref{
Id: &phoneMetaBaseId,
}
phoneConfig.PhoneMetaBase = phoneMetaBase
if isStandalone {
if phoneConfig.Properties == nil {
phoneConfig.Properties = &map[string]interface{}{}
}
phoneStandalone := map[string]interface{}{
"value": &map[string]interface{}{
"instance": true,
},
}
(*phoneConfig.Properties)["phone_standalone"] = phoneStandalone
}
webRtcUserId := d.Get("web_rtc_user_id")
if webRtcUserId != "" {
phoneConfig.WebRtcUser = util.BuildSdkDomainEntityRef(d, "web_rtc_user_id")
}
return phoneConfig, nil
}
func getLineBaseSettingsID(ctx context.Context, pp *phoneProxy, phoneBaseSettingsId string) (string, error) {
phoneBase, _, err := pp.getPhoneBaseSetting(ctx, phoneBaseSettingsId)
if err != nil {
return "", err
}
if len(*phoneBase.Lines) == 0 {
return "", nil
}
return *(*phoneBase.Lines)[0].Id, nil
}
func assignUserToWebRtcPhone(ctx context.Context, pp *phoneProxy, userId string) diag.Diagnostics {
stationId := ""
stationIsAssociated := false
retryErr := util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
station, retryable, resp, err := pp.getStationOfUser(ctx, userId)
if err != nil && !retryable {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting stations: %s", err), resp))
}
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no stations found with userID %v", userId), resp))
}
stationId = *station.Id
stationIsAssociated = *station.Status == "ASSOCIATED"
return nil
})
if retryErr != nil {
return retryErr
}
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
if stationIsAssociated {
log.Printf("Disassociating user from phone station %s", stationId)
if resp, err := pp.unassignUserFromStation(ctx, stationId); err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Error unassigning user from station %s: %v", stationId, err), resp)
}
}
resp, putErr := pp.assignUserToStation(ctx, userId, stationId)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to assign user %v to the station %s: %s", userId, stationId, putErr), resp)
}
resp, putErr = pp.assignStationAsDefault(ctx, userId, stationId)
if putErr != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to assign Station %v as the default station for user %s: %s", stationId, userId, putErr), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
return nil
}
func buildSdkPhoneBaseSettings(d *schema.ResourceData, idAttr string) *platformclientv2.Phonebasesettings {
idVal := d.Get(idAttr).(string)
if idVal == "" {
return nil
}
return &platformclientv2.Phonebasesettings{Id: &idVal}
}
func getPhoneMetaBaseId(ctx context.Context, pp *phoneProxy, phoneBaseSettingsId string) (string, error) {
phoneBase, _, err := pp.getPhoneBaseSetting(ctx, phoneBaseSettingsId)
if err != nil {
return "", err
}
return *phoneBase.PhoneMetaBase.Id, nil
}
func flattenPhoneLines(lines *[]platformclientv2.Line) []string {
if lines == nil {
return nil
}
lineAddressList := []string{}
for i := 0; i < len(*lines); i++ {
line := (*lines)[i]
did := ""
if k := (*line.Properties)["station_identity_address"]; k != nil {
didI := k.(map[string]interface{})["value"].(map[string]interface{})["instance"]
if didI != nil {
did = didI.(string)
}
}
if len(did) == 0 {
continue
}
lineAddressList = append(lineAddressList, did)
}
return lineAddressList
}
func flattenPhoneCapabilities(capabilities *platformclientv2.Phonecapabilities) []interface{} {
if capabilities == nil {
return nil
}
capabilitiesMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "provisions", capabilities.Provisions)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "registers", capabilities.Registers)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "dual_registers", capabilities.DualRegisters)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "hardware_id_type", capabilities.HardwareIdType)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "allow_reboot", capabilities.AllowReboot)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "no_rebalance", capabilities.NoRebalance)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "no_cloud_provisioning", capabilities.NoCloudProvisioning)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "media_codecs", capabilities.MediaCodecs)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "cdm", capabilities.Cdm)
return []interface{}{capabilitiesMap}
}
func buildSdkLines(ctx context.Context, pp *phoneProxy, d *schema.ResourceData, lineBaseSettings *platformclientv2.Domainentityref) (linesPtr *[]platformclientv2.Line, isStandAlone bool) {
lines := []platformclientv2.Line{}
isStandAlone = false
lineAddresses, ok := d.GetOk("line_addresses")
lineStringList := lists.InterfaceListToStrings(lineAddresses.([]interface{}))
// If line_addresses is not provided, phone is not standalone
if !ok || len(lineStringList) == 0 {
hasher := fnv.New32()
hasher.Write([]byte(d.Get("name").(string)))
lineName := "line_" + *lineBaseSettings.Id + fmt.Sprintf("%x", hasher.Sum32())
line := platformclientv2.Line{
Name: &lineName,
LineBaseSettings: lineBaseSettings,
}
// If this function is invoked on a phone create, the ID won't exist yet
if d.Id() != "" {
lineId, err := getLineIdByPhoneId(ctx, pp, d.Id())
if err != nil {
log.Printf("Failed to retrieve ID for phone %s: %v", d.Id(), err)
} else {
line.Id = &lineId
}
}
lines = append(lines, line)
linesPtr = &lines
return
}
for i := 0; i < len(lineStringList); i++ {
lineName := "line_" + *lineBaseSettings.Id + "_" + strconv.Itoa(i+1)
properties := map[string]interface{}{
"station_identity_address": &map[string]interface{}{
"value": &map[string]interface{}{
"instance": (lineStringList)[i],
},
},
}
lines = append(lines, platformclientv2.Line{
Name: &lineName,
LineBaseSettings: lineBaseSettings,
Properties: &properties,
})
}
linesPtr = &lines
isStandAlone = true
return
}
func getLineIdByPhoneId(ctx context.Context, pp *phoneProxy, phoneId string) (string, error) {
phone, _, err := pp.getPhoneById(ctx, phoneId)
if err != nil {
return "", err
}
if phone.Lines != nil && len(*phone.Lines) > 0 {
return *(*phone.Lines)[0].Id, nil
}
return "", fmt.Errorf("could not access line ID for phone %s", phoneId)
}
func buildSdkCapabilities(d *schema.ResourceData) *platformclientv2.Phonecapabilities {
if capabilities := d.Get("capabilities").([]interface{}); capabilities != nil {
sdkPhoneCapabilities := platformclientv2.Phonecapabilities{}
if len(capabilities) > 0 {
if _, ok := capabilities[0].(map[string]interface{}); !ok {
return nil
}
capabilitiesMap := capabilities[0].(map[string]interface{})
sdkPhoneCapabilities = platformclientv2.Phonecapabilities{
Provisions: platformclientv2.Bool(capabilitiesMap["provisions"].(bool)),
Registers: platformclientv2.Bool(capabilitiesMap["registers"].(bool)),
DualRegisters: platformclientv2.Bool(capabilitiesMap["dual_registers"].(bool)),
AllowReboot: platformclientv2.Bool(capabilitiesMap["allow_reboot"].(bool)),
NoRebalance: platformclientv2.Bool(capabilitiesMap["no_rebalance"].(bool)),
NoCloudProvisioning: platformclientv2.Bool(capabilitiesMap["no_cloud_provisioning"].(bool)),
Cdm: platformclientv2.Bool(capabilitiesMap["cdm"].(bool)),
}
// Hardware ID type
if checkHardwareIdType := capabilitiesMap["hardware_id_type"].(string); len(checkHardwareIdType) > 0 {
sdkPhoneCapabilities.HardwareIdType = &checkHardwareIdType
}
// Media codecs
mediaCodecs := make([]string, 0)
if checkMediaCodecs := capabilitiesMap["media_codecs"].([]interface{}); len(checkMediaCodecs) > 0 {
for _, codec := range checkMediaCodecs {
mediaCodecs = append(mediaCodecs, fmt.Sprintf("%v", codec))
}
}
sdkPhoneCapabilities.MediaCodecs = &mediaCodecs
}
return &sdkPhoneCapabilities
}
return nil
}
func GeneratePhoneResourceWithCustomAttrs(config *PhoneConfig, otherAttrs ...string) string {
lineStrs := make([]string, len(config.LineAddresses))
for i, val := range config.LineAddresses {
lineStrs[i] = fmt.Sprintf("\"%s\"", val)
}
webRtcUser := ""
if len(config.WebRtcUserId) != 0 {
webRtcUser = fmt.Sprintf(`web_rtc_user_id = %s`, config.WebRtcUserId)
}
finalConfig := fmt.Sprintf(`resource "genesyscloud_telephony_providers_edges_phone" "%s" {
name = "%s"
state = "%s"
site_id = %s
phone_base_settings_id = %s
line_addresses = [%s]
depends_on=[%s]
%s
%s
}
`, config.PhoneRes,
config.Name,
config.State,
config.SiteId,
config.PhoneBaseSettingsId,
strings.Join(lineStrs, ","),
config.DependsOn,
webRtcUser,
strings.Join(otherAttrs, "\n"),
)
return finalConfig
}
func TestVerifyWebRtcPhoneDestroyed(state *terraform.State) error {
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApi()
for _, rs := range state.RootModule().Resources {
if rs.Type != "genesyscloud_telephony_providers_edges_phone" {
continue
}
phone, resp, err := edgesAPI.GetTelephonyProvidersEdgesPhone(rs.Primary.ID)
if phone != nil {
return fmt.Errorf("phone (%s) still exists", rs.Primary.ID)
} else if util.IsStatus404(resp) {
// Phone not found as expected
continue
} else {
// Unexpected error
return fmt.Errorf("unexpected error: %s", err)
}
}
//Success. Phone destroyed
return nil
}
func generatePhoneProperties(hardware_id string) string {
// A random selection of properties
return "properties = " + util.GenerateJsonEncodedProperties(
util.GenerateJsonProperty(
"phone_hardwareId", util.GenerateJsonObject(
util.GenerateJsonProperty(
"value", util.GenerateJsonObject(
util.GenerateJsonProperty("instance", strconv.Quote(hardware_id)),
)))),
)
}
package telephony_providers_edges_phonebasesettings
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourcePhoneBaseSettingsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 50
phoneBaseSettings, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesPhonebasesettings(pageSize, pageNum, "", "", nil, name)
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting phone base settings %s | error: %s", name, getErr), resp))
}
if phoneBaseSettings.Entities == nil || len(*phoneBaseSettings.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No phoneBaseSettings found with name %s", name), resp))
}
for _, phoneBaseSetting := range *phoneBaseSettings.Entities {
if phoneBaseSetting.Name != nil && *phoneBaseSetting.Name == name &&
phoneBaseSetting.State != nil && *phoneBaseSetting.State != "deleted" {
d.SetId(*phoneBaseSetting.Id)
return nil
}
}
}
})
}
package telephony_providers_edges_phonebasesettings
import (
"context"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *phoneBaseProxy
type getPhoneBaseSettingFunc func(ctx context.Context, p *phoneBaseProxy, phoneBaseSettingsId string) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error)
type deletePhoneBaseSettingFunc func(ctx context.Context, p *phoneBaseProxy, phoneBaseSettingsId string) (*platformclientv2.APIResponse, error)
type putPhoneBaseSettingFunc func(ctx context.Context, p *phoneBaseProxy, phoneBaseSettingsId string, body platformclientv2.Phonebase) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error)
type postPhoneBaseSettingFunc func(ctx context.Context, p *phoneBaseProxy, body platformclientv2.Phonebase) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error)
type getAllPhoneBaseSettingsFunc func(ctx context.Context, p *phoneBaseProxy) (*[]platformclientv2.Phonebase, *platformclientv2.APIResponse, error)
// PhoneBaseSettinProxy contains all of the methods that call genesys cloud APIs.
type phoneBaseProxy struct {
clientConfig *platformclientv2.Configuration
edgesApi *platformclientv2.TelephonyProvidersEdgeApi
getPhoneBaseSettingAttr getPhoneBaseSettingFunc
deletePhoneBaseSettingAttr deletePhoneBaseSettingFunc
putPhoneBaseSettingAttr putPhoneBaseSettingFunc
postPhoneBaseSettingAttr postPhoneBaseSettingFunc
getAllPhoneBaseSettingsAttr getAllPhoneBaseSettingsFunc
}
// newPhoneBaseSettinProxy initializes the Phone Base Setting proxy with all of the data needed to communicate with Genesys Cloud
func newphoneBaseProxy(clientConfig *platformclientv2.Configuration) *phoneBaseProxy {
edgesApi := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
return &phoneBaseProxy{
clientConfig: clientConfig,
edgesApi: edgesApi,
getPhoneBaseSettingAttr: getPhoneBaseSettingFn,
deletePhoneBaseSettingAttr: deletePhoneBaseSettingsFn,
putPhoneBaseSettingAttr: putPhoneBaseSettingFn,
postPhoneBaseSettingAttr: postPhoneBaseSettingFn,
getAllPhoneBaseSettingsAttr: getAllPhoneBaseSettingsFn,
}
}
// getPhoneBaseProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getPhoneBaseProxy(clientConfig *platformclientv2.Configuration) *phoneBaseProxy {
if internalProxy == nil {
internalProxy = newphoneBaseProxy(clientConfig)
}
return internalProxy
}
func (p *phoneBaseProxy) getPhoneBaseSetting(ctx context.Context, phoneBaseSettingsId string) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
return p.getPhoneBaseSettingAttr(ctx, p, phoneBaseSettingsId)
}
func (p *phoneBaseProxy) deletePhoneBaseSetting(ctx context.Context, phoneBaseSettingsId string) (*platformclientv2.APIResponse, error) {
return p.deletePhoneBaseSettingAttr(ctx, p, phoneBaseSettingsId)
}
func (p *phoneBaseProxy) putPhoneBaseSetting(ctx context.Context, phoneBaseSettingsId string, body platformclientv2.Phonebase) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
return p.putPhoneBaseSettingAttr(ctx, p, phoneBaseSettingsId, body)
}
func (p *phoneBaseProxy) postPhoneBaseSetting(ctx context.Context, body platformclientv2.Phonebase) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
return p.postPhoneBaseSettingAttr(ctx, p, body)
}
func (p *phoneBaseProxy) getAllPhoneBaseSettings(ctx context.Context) (*[]platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
return p.getAllPhoneBaseSettingsAttr(ctx, p)
}
// getPhoneBaseSettingFn is an implementation function for retrieving a Genesys Cloud Phone Base Setting
func getPhoneBaseSettingFn(ctx context.Context, p *phoneBaseProxy, phoneBaseSettingsId string) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
phoneBase, resp, err := p.edgesApi.GetTelephonyProvidersEdgesPhonebasesetting(phoneBaseSettingsId)
if err != nil {
return nil, resp, err
}
return phoneBase, resp, nil
}
func deletePhoneBaseSettingsFn(ctx context.Context, p *phoneBaseProxy, phoneBaseSettingsId string) (*platformclientv2.APIResponse, error) {
resp, err := p.edgesApi.DeleteTelephonyProvidersEdgesPhonebasesetting(phoneBaseSettingsId)
return resp, err
}
func putPhoneBaseSettingFn(ctx context.Context, p *phoneBaseProxy, phoneBaseSettingsId string, body platformclientv2.Phonebase) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
phoneBase, resp, err := p.edgesApi.PutTelephonyProvidersEdgesPhonebasesetting(phoneBaseSettingsId, body)
if err != nil {
return nil, resp, err
}
return phoneBase, resp, nil
}
func postPhoneBaseSettingFn(ctx context.Context, p *phoneBaseProxy, body platformclientv2.Phonebase) (*platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
phoneBase, resp, err := p.edgesApi.PostTelephonyProvidersEdgesPhonebasesettings(body)
if err != nil {
return nil, resp, err
}
return phoneBase, resp, nil
}
func getAllPhoneBaseSettingsFn(ctx context.Context, p *phoneBaseProxy) (*[]platformclientv2.Phonebase, *platformclientv2.APIResponse, error) {
const pageSize = 100
var allPhoneBaseSettings []platformclientv2.Phonebase
var response *platformclientv2.APIResponse
for pageNum := 1; ; pageNum++ {
phoneBaseSettings, resp, err := p.edgesApi.GetTelephonyProvidersEdgesPhonebasesettings(pageSize, pageNum, "", "", nil, "")
if err != nil {
return nil, resp, err
}
response = resp
if phoneBaseSettings.Entities == nil || len(*phoneBaseSettings.Entities) == 0 {
break
}
for _, phoneBaseSetting := range *phoneBaseSettings.Entities {
if phoneBaseSetting.State != nil && *phoneBaseSetting.State != "deleted" {
allPhoneBaseSettings = append(allPhoneBaseSettings, phoneBaseSetting)
}
}
}
return &allPhoneBaseSettings, response, nil
}
package telephony_providers_edges_phonebasesettings
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
)
const (
resourceName = "genesyscloud_telephony_providers_edges_phonebasesettings"
)
var (
phoneCapabilities = &schema.Resource{
Schema: map[string]*schema.Schema{
"provisions": {
Description: "Provisions",
Type: schema.TypeBool,
Optional: true,
},
"registers": {
Description: "Registers",
Type: schema.TypeBool,
Optional: true,
},
"dual_registers": {
Description: "Dual Registers",
Type: schema.TypeBool,
Optional: true,
},
"hardware_id_type": {
Description: "HardwareId Type",
Type: schema.TypeString,
Optional: true,
},
"allow_reboot": {
Description: "Allow Reboot",
Type: schema.TypeBool,
Optional: true,
},
"no_rebalance": {
Description: "No Rebalance",
Type: schema.TypeBool,
Optional: true,
},
"no_cloud_provisioning": {
Description: "No Cloud Provisioning",
Type: schema.TypeBool,
Optional: true,
},
"media_codecs": {
Description: "Media Codecs",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"audio/opus", "audio/pcmu", "audio/pcma", "audio/g729", "audio/g722"}, false),
},
},
"cdm": {
Description: "CDM",
Type: schema.TypeBool,
Optional: true,
},
},
}
)
func ResourcePhoneBaseSettings() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Phone Base Settings",
CreateContext: provider.CreateWithPooledClient(createPhoneBaseSettings),
ReadContext: provider.ReadWithPooledClient(readPhoneBaseSettings),
UpdateContext: provider.UpdateWithPooledClient(updatePhoneBaseSettings),
DeleteContext: provider.DeleteWithPooledClient(deletePhoneBaseSettings),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "The resource's description.",
Type: schema.TypeString,
Optional: true,
},
"phone_meta_base_id": {
Description: "A phone metabase is essentially a database for storing phone configuration settings, which simplifies the configuration process.",
Type: schema.TypeString,
Required: true,
},
"properties": {
Description: "phone base settings properties",
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: util.SuppressEquivalentJsonDiffs,
},
"capabilities": {
Description: "Phone Capabilities.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: phoneCapabilities,
},
"line_base_settings_id": {
Description: "Computed line base settings id",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
CustomizeDiff: util.CustomizePhoneBaseSettingsPropertiesDiff,
}
}
func DataSourcePhoneBaseSettings() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Phone Base Settings. Select a phone base settings by name",
ReadContext: provider.ReadWithPooledClient(dataSourcePhoneBaseSettingsRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Phone Base Settings name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func PhoneBaseSettingsExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllPhoneBaseSettings),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{},
JsonEncodeAttributes: []string{"properties"},
}
}
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource("genesyscloud_telephony_providers_edges_phonebasesettings", DataSourcePhoneBaseSettings())
l.RegisterResource("genesyscloud_telephony_providers_edges_phonebasesettings", ResourcePhoneBaseSettings())
l.RegisterExporter("genesyscloud_telephony_providers_edges_phonebasesettings", PhoneBaseSettingsExporter())
}
package telephony_providers_edges_phonebasesettings
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func createPhoneBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
phoneMetaBase := util.BuildSdkDomainEntityRef(d, "phone_meta_base_id")
properties := util.BuildTelephonyProperties(d)
phoneBase := platformclientv2.Phonebase{
Name: &name,
PhoneMetaBase: phoneMetaBase,
Properties: properties,
Lines: &[]platformclientv2.Linebase{
{
Name: &name,
LineMetaBase: phoneMetaBase,
},
},
Capabilities: buildSdkCapabilities(d),
}
if description != "" {
phoneBase.Description = &description
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
phoneBaseProxy := getPhoneBaseProxy(sdkConfig)
log.Printf("Creating phone base settings %s", name)
phoneBaseSettings, resp, err := phoneBaseProxy.postPhoneBaseSetting(ctx, phoneBase)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create phone base settings %s error: %s", name, err), resp)
}
d.SetId(*phoneBaseSettings.Id)
log.Printf("Created phone base settings %s", *phoneBaseSettings.Id)
return readPhoneBaseSettings(ctx, d, meta)
}
func updatePhoneBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
phoneMetaBase := util.BuildSdkDomainEntityRef(d, "phone_meta_base_id")
properties := util.BuildTelephonyProperties(d)
id := d.Id()
phoneBase := platformclientv2.Phonebase{
Id: &id,
Name: &name,
PhoneMetaBase: phoneMetaBase,
Properties: properties,
Lines: &[]platformclientv2.Linebase{
{
Name: &name,
LineMetaBase: phoneMetaBase,
},
},
Capabilities: buildSdkCapabilities(d),
}
if description != "" {
phoneBase.Description = &description
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
phoneBaseProxy := getPhoneBaseProxy(sdkConfig)
phoneBaseSettings, resp, getErr := phoneBaseProxy.getPhoneBaseSetting(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read phone base settings %s | error: %s", d.Id(), getErr), resp)
}
(*phoneBase.Lines)[0].Id = (*phoneBaseSettings.Lines)[0].Id
log.Printf("Updating phone base settings %s", name)
phoneBaseSettings, resp, err := phoneBaseProxy.putPhoneBaseSetting(ctx, d.Id(), phoneBase)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update phone base settings %s error: %s", name, err), resp)
}
log.Printf("Updated phone base settings %s", d.Id())
return readPhoneBaseSettings(ctx, d, meta)
}
func readPhoneBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
phoneBaseProxy := getPhoneBaseProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourcePhoneBaseSettings(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading phone base settings %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
phoneBaseSettings, resp, getErr := phoneBaseProxy.getPhoneBaseSetting(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read phone base settings %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read phone base settings %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *phoneBaseSettings.Name)
resourcedata.SetNillableValue(d, "description", phoneBaseSettings.Description)
if phoneBaseSettings.PhoneMetaBase != nil {
d.Set("phone_meta_base_id", *phoneBaseSettings.PhoneMetaBase.Id)
}
d.Set("properties", nil)
if phoneBaseSettings.Properties != nil {
properties, err := util.FlattenTelephonyProperties(phoneBaseSettings.Properties)
if err != nil {
return retry.NonRetryableError(fmt.Errorf("%v", err))
}
d.Set("properties", properties)
}
if phoneBaseSettings.Capabilities != nil {
d.Set("capabilities", flattenPhoneCapabilities(phoneBaseSettings.Capabilities))
}
if len(*phoneBaseSettings.Lines) > 0 {
d.Set("line_base_settings_id", (*phoneBaseSettings.Lines)[0].Id)
}
log.Printf("Read phone base settings %s %s", d.Id(), *phoneBaseSettings.Name)
return cc.CheckState(d)
})
}
func deletePhoneBaseSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
phoneBaseProxy := getPhoneBaseProxy(sdkConfig)
log.Printf("Deleting phone base settings")
resp, err := phoneBaseProxy.deletePhoneBaseSetting(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete phone base settings %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
phoneBaseSettings, resp, err := phoneBaseProxy.getPhoneBaseSetting(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Phone base proxy settings deleted
log.Printf("Deleted Phone base settings %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Phone base settings %s | error: %s", d.Id(), err), resp))
}
if phoneBaseSettings.State != nil && *phoneBaseSettings.State == "deleted" {
// Phone base proxy settings deleted
log.Printf("Deleted Phone base settings %s", d.Id())
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("phone base settings %s still exists", d.Id()), resp))
})
}
func getAllPhoneBaseSettings(ctx context.Context, sdkConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
phoneBaseProxy := getPhoneBaseProxy(sdkConfig)
phoneBaseSettings, resp, err := phoneBaseProxy.getAllPhoneBaseSettings(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get all phone base settings error: %s", err), resp)
}
if phoneBaseSettings != nil {
for _, phoneBaseSetting := range *phoneBaseSettings {
resources[*phoneBaseSetting.Id] = &resourceExporter.ResourceMeta{Name: *phoneBaseSetting.Name}
}
}
return resources, nil
}
package telephony_providers_edges_phonebasesettings
import (
"fmt"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func generatePhoneBaseSettingsDataSource(
resourceID string,
name string,
// Must explicitly use depends_on in terraform v0.13 when a data source references a resource
// Fixed in v0.14 https://github.com/hashicorp/terraform/pull/26284
dependsOnResource string) string {
return fmt.Sprintf(`data "genesyscloud_telephony_providers_edges_phonebasesettings" "%s" {
name = "%s"
depends_on=[%s]
}
`, resourceID, name, dependsOnResource)
}
func buildSdkCapabilities(d *schema.ResourceData) *platformclientv2.Phonecapabilities {
if capabilities := d.Get("capabilities").([]interface{}); capabilities != nil {
sdkPhoneCapabilities := platformclientv2.Phonecapabilities{}
if len(capabilities) > 0 {
if _, ok := capabilities[0].(map[string]interface{}); !ok {
return nil
}
capabilitiesMap := capabilities[0].(map[string]interface{})
// Only set non-empty values.
provisions := capabilitiesMap["provisions"].(bool)
registers := capabilitiesMap["registers"].(bool)
dualRegisters := capabilitiesMap["dual_registers"].(bool)
var hardwareIdType string
if checkHardwareIdType := capabilitiesMap["hardware_id_type"].(string); len(checkHardwareIdType) > 0 {
hardwareIdType = checkHardwareIdType
}
allowReboot := capabilitiesMap["allow_reboot"].(bool)
noRebalance := capabilitiesMap["no_rebalance"].(bool)
noCloudProvisioning := capabilitiesMap["no_cloud_provisioning"].(bool)
mediaCodecs := make([]string, 0)
if checkMediaCodecs := capabilitiesMap["media_codecs"].([]interface{}); len(checkMediaCodecs) > 0 {
for _, codec := range checkMediaCodecs {
mediaCodecs = append(mediaCodecs, fmt.Sprintf("%v", codec))
}
}
cdm := capabilitiesMap["cdm"].(bool)
sdkPhoneCapabilities = platformclientv2.Phonecapabilities{
Provisions: &provisions,
Registers: ®isters,
DualRegisters: &dualRegisters,
HardwareIdType: &hardwareIdType,
AllowReboot: &allowReboot,
NoRebalance: &noRebalance,
NoCloudProvisioning: &noCloudProvisioning,
MediaCodecs: &mediaCodecs,
Cdm: &cdm,
}
}
return &sdkPhoneCapabilities
}
return nil
}
func flattenPhoneCapabilities(capabilities *platformclientv2.Phonecapabilities) []interface{} {
if capabilities == nil {
return nil
}
capabilitiesMap := make(map[string]interface{})
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "provisions", capabilities.Provisions)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "registers", capabilities.Registers)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "dual_registers", capabilities.DualRegisters)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "hardware_id_type", capabilities.HardwareIdType)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "allow_reboot", capabilities.AllowReboot)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "no_rebalance", capabilities.NoRebalance)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "no_cloud_provisioning", capabilities.NoCloudProvisioning)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "media_codecs", capabilities.MediaCodecs)
resourcedata.SetMapValueIfNotNil(capabilitiesMap, "cdm", capabilities.Cdm)
return []interface{}{capabilitiesMap}
}
func GeneratePhoneBaseSettingsResourceWithCustomAttrs(
phoneBaseSettingsRes,
name,
description,
phoneMetaBaseId string,
otherAttrs ...string) string {
return fmt.Sprintf(`resource "genesyscloud_telephony_providers_edges_phonebasesettings" "%s" {
name = "%s"
description = "%s"
phone_meta_base_id = "%s"
%s
}
`, phoneBaseSettingsRes, name, description, phoneMetaBaseId, strings.Join(otherAttrs, "\n"))
}
package telephony_providers_edges_site
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceSiteRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
sp := getSiteProxy(sdkConfig)
name := d.Get("name").(string)
managed := d.Get("managed").(bool)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
siteId, retryable, resp, err := sp.getSiteIdByName(ctx, name, managed)
if err != nil {
if retryable {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get site %s", name), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting site %s | error: %s", name, err), resp))
}
d.SetId(siteId)
return nil
})
}
package telephony_providers_edges_site
import (
"context"
"fmt"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
The genesyscloud_telephony_providers_edges_site_proxy.go file contains the proxy structures and methods that interact
with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed
out during testing.
Each proxy implementation:
1. Should provide a private package level variable that holds a instance of a proxy class.
2. A New... constructor function to initialize the proxy object. This constructor should only be used within
the proxy.
3. A get private constructor function that the classes in the package can be used to to retrieve
the proxy. This proxy should check to see if the package level proxy instance is nil and
should initialize it, otherwise it should return the instance
4. Type definitions for each function that will be used in the proxy. We use composition here
so that we can easily provide mocks for testing.
5. A struct for the proxy that holds an attribute for each function type.
6. Wrapper methods on each of the elements on the struct.
7. Function implementations for each function type definition.
*/
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *siteProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getAllManagedSitesFunc func(ctx context.Context, p *siteProxy) (*[]platformclientv2.Site, *platformclientv2.APIResponse, error)
type getAllUnmanagedSitesFunc func(ctx context.Context, p *siteProxy) (*[]platformclientv2.Site, *platformclientv2.APIResponse, error)
type createSiteFunc func(ctx context.Context, p *siteProxy, site *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error)
type deleteSiteFunc func(ctx context.Context, p *siteProxy, siteId string) (*platformclientv2.APIResponse, error)
type getSiteByIdFunc func(ctx context.Context, p *siteProxy, siteId string) (site *platformclientv2.Site, resp *platformclientv2.APIResponse, err error)
type getSiteIdByNameFunc func(ctx context.Context, p *siteProxy, siteName string, managed bool) (siteId string, retryable bool, resp *platformclientv2.APIResponse, err error)
type updateSiteFunc func(ctx context.Context, p *siteProxy, siteId string, site *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error)
type createSiteOutboundRouteFunc func(ctx context.Context, p *siteProxy, siteId string, outboundRoute *platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error)
type getSiteOutboundRoutesFunc func(ctx context.Context, p *siteProxy, siteId string) (*[]platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error)
type updateSiteOutboundRouteFunc func(ctx context.Context, p *siteProxy, siteId string, outboundRouteId string, outboundRoute *platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error)
type deleteSiteOutboundRouteFunc func(ctx context.Context, p *siteProxy, siteId string, outboundRouteId string) (*platformclientv2.APIResponse, error)
type getSiteNumberPlansFunc func(ctx context.Context, p *siteProxy, siteId string) (*[]platformclientv2.Numberplan, *platformclientv2.APIResponse, error)
type updateSiteNumberPlansFunc func(ctx context.Context, p *siteProxy, siteId string, numberPlans *[]platformclientv2.Numberplan) (*[]platformclientv2.Numberplan, *platformclientv2.APIResponse, error)
type getLocationFunc func(ctx context.Context, p *siteProxy, locationId string) (*platformclientv2.Locationdefinition, *platformclientv2.APIResponse, error)
type getTelephonyMediaregionsFunc func(ctx context.Context, p *siteProxy) (*platformclientv2.Mediaregions, *platformclientv2.APIResponse, error)
type setDefaultSiteFunc func(ctx context.Context, p *siteProxy, siteId string) (*platformclientv2.APIResponse, error)
type getDefaultSiteIdFunc func(ctx context.Context, p *siteProxy) (siteId string, resp *platformclientv2.APIResponse, err error)
// siteProxy contains all of the methods that call genesys cloud APIs.
type siteProxy struct {
clientConfig *platformclientv2.Configuration
edgesApi *platformclientv2.TelephonyProvidersEdgeApi
locationsApi *platformclientv2.LocationsApi
telephonyApi *platformclientv2.TelephonyApi
organizationApi *platformclientv2.OrganizationApi
getAllManagedSitesAttr getAllManagedSitesFunc
getAllUnmanagedSitesAttr getAllUnmanagedSitesFunc
createSiteAttr createSiteFunc
deleteSiteAttr deleteSiteFunc
getSiteByIdAttr getSiteByIdFunc
getSiteIdByNameAttr getSiteIdByNameFunc
updateSiteAttr updateSiteFunc
createSiteOutboundRouteAttr createSiteOutboundRouteFunc
getSiteOutboundRoutesAttr getSiteOutboundRoutesFunc
updateSiteOutboundRouteAttr updateSiteOutboundRouteFunc
deleteSiteOutboundRouteAttr deleteSiteOutboundRouteFunc
getSiteNumberPlansAttr getSiteNumberPlansFunc
updateSiteNumberPlansAttr updateSiteNumberPlansFunc
getLocationAttr getLocationFunc
getTelephonyMediaregionsAttr getTelephonyMediaregionsFunc
setDefaultSiteAttr setDefaultSiteFunc
getDefaultSiteIdAttr getDefaultSiteIdFunc
}
// newSiteProxy initializes the Site proxy with all the data needed to communicate with Genesys Cloud
func newSiteProxy(clientConfig *platformclientv2.Configuration) *siteProxy {
edgesApi := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
locationsApi := platformclientv2.NewLocationsApiWithConfig(clientConfig)
telephonyApi := platformclientv2.NewTelephonyApiWithConfig(clientConfig)
organizationApi := platformclientv2.NewOrganizationApiWithConfig(clientConfig)
return &siteProxy{
clientConfig: clientConfig,
edgesApi: edgesApi,
locationsApi: locationsApi,
telephonyApi: telephonyApi,
organizationApi: organizationApi,
getAllManagedSitesAttr: getAllManagedSitesFn,
getAllUnmanagedSitesAttr: getAllUnmanagedSitesFn,
createSiteAttr: createSiteFn,
deleteSiteAttr: deleteSiteFn,
getSiteByIdAttr: getSiteByIdFn,
getSiteIdByNameAttr: getSiteIdByNameFn,
updateSiteAttr: updateSiteFn,
createSiteOutboundRouteAttr: createSiteOutboundRouteFn,
getSiteOutboundRoutesAttr: getSiteOutboundRoutesFn,
updateSiteOutboundRouteAttr: updateSiteOutboundRouteFn,
deleteSiteOutboundRouteAttr: deleteSiteOutboundRouteFn,
getSiteNumberPlansAttr: getSiteNumberPlansFn,
updateSiteNumberPlansAttr: updateSiteNumberPlansFn,
getLocationAttr: getLocationFn,
getTelephonyMediaregionsAttr: getTelephonyMediaregionsFn,
setDefaultSiteAttr: setDefaultSiteFn,
getDefaultSiteIdAttr: getDefaultSiteIdFn,
}
}
// getSiteProxy acts as a singleton for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getSiteProxy(clientConfig *platformclientv2.Configuration) *siteProxy {
if internalProxy == nil {
internalProxy = newSiteProxy(clientConfig)
}
return internalProxy
}
// getAllManagedSitesFunc retrieves all managed Genesys Cloud Sites
func (p *siteProxy) getAllManagedSites(ctx context.Context) (*[]platformclientv2.Site, *platformclientv2.APIResponse, error) {
return p.getAllManagedSitesAttr(ctx, p)
}
// getAllUnmanagedSitesFunc retrieves all unmanaged Genesys Cloud Sites
func (p *siteProxy) getAllUnmanagedSites(ctx context.Context) (*[]platformclientv2.Site, *platformclientv2.APIResponse, error) {
return p.getAllUnmanagedSitesAttr(ctx, p)
}
// createSiteFunc creates a Genesys Cloud Site
func (p *siteProxy) createSite(ctx context.Context, site *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error) {
return p.createSiteAttr(ctx, p, site)
}
// deleteSiteFunc deletes a Genesys Cloud Site by ID
func (p *siteProxy) deleteSite(ctx context.Context, siteId string) (*platformclientv2.APIResponse, error) {
return p.deleteSiteAttr(ctx, p, siteId)
}
// getSiteByIdFunc returns a single Genesys Cloud Site by Id
func (p *siteProxy) getSiteById(ctx context.Context, siteId string) (site *platformclientv2.Site, resp *platformclientv2.APIResponse, err error) {
return p.getSiteByIdAttr(ctx, p, siteId)
}
// getSiteIdByNameFunc returns a single Genesys Cloud Site by Name
func (p *siteProxy) getSiteIdByName(ctx context.Context, siteName string, managed bool) (siteId string, retryable bool, resp *platformclientv2.APIResponse, err error) {
return p.getSiteIdByNameAttr(ctx, p, siteName, managed)
}
// updateSiteFunc updates a Genesys Cloud Site
func (p *siteProxy) updateSite(ctx context.Context, siteId string, site *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error) {
return p.updateSiteAttr(ctx, p, siteId, site)
}
// createSiteOutboundRouteFunc creates an Outbound Route for a Genesys Cloud Site
func (p *siteProxy) createSiteOutboundRoute(ctx context.Context, siteId string, outboundRoute *platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error) {
return p.createSiteOutboundRouteAttr(ctx, p, siteId, outboundRoute)
}
// getSiteByIdFunc returns a single Outbound Route by Id
func (p *siteProxy) getSiteOutboundRoutes(ctx context.Context, siteId string) (*[]platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error) {
return p.getSiteOutboundRoutesAttr(ctx, p, siteId)
}
// updateSiteFunc updates a Genesys Cloud Outbound Route for a Genesys Cloud Site
func (p *siteProxy) updateSiteOutboundRoute(ctx context.Context, siteId string, outboundRouteId string, outboundRoute *platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error) {
return p.updateSiteOutboundRouteAttr(ctx, p, siteId, outboundRouteId, outboundRoute)
}
// deleteSiteFunc deletes a Genesys Cloud Outbound Route by Id for a Genesys Cloud Site
func (p *siteProxy) deleteSiteOutboundRoute(ctx context.Context, siteId string, outboundRouteId string) (*platformclientv2.APIResponse, error) {
return p.deleteSiteOutboundRouteAttr(ctx, p, siteId, outboundRouteId)
}
// getSiteNumberPlansFunc retrieves all Number Plans of a Genesys Cloud Sites
func (p *siteProxy) getSiteNumberPlans(ctx context.Context, siteId string) (*[]platformclientv2.Numberplan, *platformclientv2.APIResponse, error) {
return p.getSiteNumberPlansAttr(ctx, p, siteId)
}
// updateSiteNumberPlansFunc updates the Number Plans for a Genesys Cloud Site
func (p *siteProxy) updateSiteNumberPlans(ctx context.Context, siteId string, numberPlans *[]platformclientv2.Numberplan) (*[]platformclientv2.Numberplan, *platformclientv2.APIResponse, error) {
return p.updateSiteNumberPlansAttr(ctx, p, siteId, numberPlans)
}
// getLocation retrieves a Genesys Cloud Location by Id
func (p *siteProxy) getLocation(ctx context.Context, locationId string) (*platformclientv2.Locationdefinition, *platformclientv2.APIResponse, error) {
return p.getLocationAttr(ctx, p, locationId)
}
// getTelephonyMediaregions retrieves the Genesys Cloud media regions
func (p *siteProxy) getTelephonyMediaregions(ctx context.Context) (*platformclientv2.Mediaregions, *platformclientv2.APIResponse, error) {
return p.getTelephonyMediaregionsAttr(ctx, p)
}
// setDefaultSite sets a Genesys Cloud Site as the default site for the org
func (p *siteProxy) setDefaultSite(ctx context.Context, siteId string) (*platformclientv2.APIResponse, error) {
return p.setDefaultSiteAttr(ctx, p, siteId)
}
// getDefaultSiteId gets the default Site for the Genesys Cloud org
func (p *siteProxy) getDefaultSiteId(ctx context.Context) (siteId string, resp *platformclientv2.APIResponse, err error) {
return p.getDefaultSiteIdAttr(ctx, p)
}
// getAllManagedSitesFn is an implementation function for retrieving all Genesys Cloud Outbound managed Sites
func getAllManagedSitesFn(ctx context.Context, p *siteProxy) (*[]platformclientv2.Site, *platformclientv2.APIResponse, error) {
var allManagedSites []platformclientv2.Site
const pageSize = 100
sites, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSites(pageSize, 1, "", "", "", "", true, nil)
if err != nil {
return nil, resp, err
}
// Get only sites that are not 'deleted'
for _, site := range *sites.Entities {
if site.State != nil && *site.State != "deleted" {
allManagedSites = append(allManagedSites, site)
}
}
for pageNum := 2; pageNum <= *sites.PageCount; pageNum++ {
sites, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSites(pageSize, pageNum, "", "", "", "", true, nil)
if err != nil {
return nil, resp, err
}
if sites.Entities == nil || len(*sites.Entities) == 0 {
break
}
// Get only sites that are not 'deleted'
for _, site := range *sites.Entities {
if site.State != nil && *site.State != "deleted" {
allManagedSites = append(allManagedSites, site)
}
}
}
return &allManagedSites, resp, nil
}
// getAllUnmanagedSitesFn is an implementation function for retrieving all Genesys Cloud Outbound unmanaged Sites
func getAllUnmanagedSitesFn(ctx context.Context, p *siteProxy) (*[]platformclientv2.Site, *platformclientv2.APIResponse, error) {
var allUnManagedSites []platformclientv2.Site
const pageSize = 100
sites, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSites(pageSize, 1, "", "", "", "", false, nil)
if err != nil {
return nil, resp, err
}
// Get only sites that are not 'deleted'
for _, site := range *sites.Entities {
if site.State != nil && *site.State != "deleted" {
allUnManagedSites = append(allUnManagedSites, site)
}
}
for pageNum := 2; pageNum <= *sites.PageCount; pageNum++ {
sites, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSites(pageSize, pageNum, "", "", "", "", false, nil)
if err != nil {
return nil, resp, err
}
if sites.Entities == nil || len(*sites.Entities) == 0 {
break
}
// Get only sites that are not 'deleted'
for _, site := range *sites.Entities {
if site.State != nil && *site.State != "deleted" {
allUnManagedSites = append(allUnManagedSites, site)
}
}
}
return &allUnManagedSites, resp, nil
}
// createSiteFn is an implementation function for creating a Genesys Cloud Site
func createSiteFn(ctx context.Context, p *siteProxy, siteReq *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error) {
site, resp, err := p.edgesApi.PostTelephonyProvidersEdgesSites(*siteReq)
if err != nil {
return nil, resp, err
}
return site, resp, nil
}
// deleteSiteFn is an implementation function for deleting a Genesys Cloud Site
func deleteSiteFn(ctx context.Context, p *siteProxy, siteId string) (*platformclientv2.APIResponse, error) {
resp, err := p.edgesApi.DeleteTelephonyProvidersEdgesSite(siteId)
if err != nil {
return resp, err
}
return resp, nil
}
// getSiteByIdFn is an implementation function for retrieving a Genesys Cloud Site by id
func getSiteByIdFn(ctx context.Context, p *siteProxy, siteId string) (*platformclientv2.Site, *platformclientv2.APIResponse, error) {
site, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSite(siteId)
if err != nil {
return nil, resp, err
}
return site, resp, nil
}
// getSiteIdByNameFn is an implementation function for retrieving a Genesys Cloud Site by name
func getSiteIdByNameFn(ctx context.Context, p *siteProxy, siteName string, managed bool) (string, bool, *platformclientv2.APIResponse, error) {
const pageSize = 100
sites, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSites(pageSize, 1, "", "", siteName, "", managed, nil)
if err != nil {
return "", false, resp, err
}
if sites.Entities == nil || len(*sites.Entities) == 0 {
return "", true, resp, fmt.Errorf("no sites found with name %s", siteName)
}
for _, site := range *sites.Entities {
if (site.Name != nil && *site.Name == siteName) && (site.State != nil && *site.State != "deleted") {
return *site.Id, false, resp, nil
}
}
for pageNum := 2; pageNum <= *sites.PageCount; pageNum++ {
sites, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSites(pageSize, pageNum, "", "", siteName, "", managed, nil)
if err != nil {
return "", false, resp, err
}
if sites.Entities == nil || len(*sites.Entities) == 0 {
return "", true, resp, fmt.Errorf("no sites found with name %s", siteName)
}
for _, site := range *sites.Entities {
if (site.Name != nil && *site.Name == siteName) && (site.State != nil && *site.State != "deleted") {
return *site.Id, false, resp, nil
}
}
}
return "", true, resp, fmt.Errorf("no sites found with name %s", siteName)
}
// updateSiteFn is an implementation function for updating a Genesys Cloud Site
func updateSiteFn(ctx context.Context, p *siteProxy, siteId string, site *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error) {
updatedSite, resp, err := p.edgesApi.PutTelephonyProvidersEdgesSite(siteId, *site)
if err != nil {
return nil, resp, err
}
return updatedSite, resp, nil
}
// createSiteOutboundRouteFn is an implementation function for creating an outbound route for a Genesys Cloud Site
func createSiteOutboundRouteFn(ctx context.Context, p *siteProxy, siteId string, outboundRoute *platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error) {
obr, resp, err := p.edgesApi.PostTelephonyProvidersEdgesSiteOutboundroutes(siteId, *outboundRoute)
if err != nil {
return nil, resp, err
}
return obr, resp, nil
}
// getSiteOutboundRoutesFn is an implementation function for getting an outbound route for a Genesys Cloud Site
func getSiteOutboundRoutesFn(ctx context.Context, p *siteProxy, siteId string) (*[]platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error) {
var allOutboundRoutes = []platformclientv2.Outboundroutebase{}
const pageSize = 100
outboundRoutes, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSiteOutboundroutes(siteId, pageSize, 1, "", "", "")
if err != nil {
return nil, resp, err
}
allOutboundRoutes = append(allOutboundRoutes, *outboundRoutes.Entities...)
for pageNum := 2; pageNum <= *outboundRoutes.PageCount; pageNum++ {
outboundRoutes, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSiteOutboundroutes(siteId, pageSize, pageNum, "", "", "")
if err != nil {
return nil, resp, err
}
if outboundRoutes.Entities == nil {
break
}
allOutboundRoutes = append(allOutboundRoutes, *outboundRoutes.Entities...)
}
return &allOutboundRoutes, resp, nil
}
// updateSiteOutboundRouteFn is an implementation function for updating an outbound route for a Genesys Cloud Site
func updateSiteOutboundRouteFn(ctx context.Context, p *siteProxy, siteId string, outboundRouteId string, outboundRoute *platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error) {
obrs, resp, err := p.edgesApi.PutTelephonyProvidersEdgesSiteOutboundroute(siteId, outboundRouteId, *outboundRoute)
if err != nil {
return nil, resp, err
}
return obrs, resp, nil
}
// deleteSiteOutboundRouteFn is an implementation function for deleting an outbound route for a Genesys Cloud Site
func deleteSiteOutboundRouteFn(ctx context.Context, p *siteProxy, siteId string, outboundRouteId string) (*platformclientv2.APIResponse, error) {
resp, err := p.edgesApi.DeleteTelephonyProvidersEdgesSiteOutboundroute(siteId, outboundRouteId)
if err != nil {
return resp, err
}
return resp, nil
}
// getSiteNumberPlansFn is an implementation function for retrieving number plans of a Genesys Cloud Site
func getSiteNumberPlansFn(ctx context.Context, p *siteProxy, siteId string) (*[]platformclientv2.Numberplan, *platformclientv2.APIResponse, error) {
numberPlans, resp, err := p.edgesApi.GetTelephonyProvidersEdgesSiteNumberplans(siteId)
if err != nil {
return nil, resp, err
}
return &numberPlans, resp, nil
}
// updateSiteNumberPlansFn is an implementation function for updating number plans of a Genesys Cloud Site
func updateSiteNumberPlansFn(ctx context.Context, p *siteProxy, siteId string, numberPlansUpdate *[]platformclientv2.Numberplan) (*[]platformclientv2.Numberplan, *platformclientv2.APIResponse, error) {
numberPlans, resp, err := p.edgesApi.PutTelephonyProvidersEdgesSiteNumberplans(siteId, *numberPlansUpdate)
if err != nil {
return nil, resp, err
}
return &numberPlans, resp, nil
}
// getLocationFn is an implementation function for retrieving a Genesys Cloud Location
func getLocationFn(ctx context.Context, p *siteProxy, locationId string) (*platformclientv2.Locationdefinition, *platformclientv2.APIResponse, error) {
location, resp, err := p.locationsApi.GetLocation(locationId, nil)
if err != nil {
return nil, resp, err
}
if location.EmergencyNumber == nil {
return nil, resp, fmt.Errorf("location with id %v does not have an emergency number", locationId)
}
return location, resp, nil
}
// getTelephonyMediaregionsFn is an implementation function for retrieving a Genesys Cloud Media Regions
func getTelephonyMediaregionsFn(ctx context.Context, p *siteProxy) (*platformclientv2.Mediaregions, *platformclientv2.APIResponse, error) {
telephonyRegions, resp, err := p.telephonyApi.GetTelephonyMediaregions()
if err != nil {
return nil, resp, err
}
return telephonyRegions, resp, nil
}
// setDefaultSiteFn is an implementation function for setting the default Site of a Genesys Cloud org
func setDefaultSiteFn(ctx context.Context, p *siteProxy, siteId string) (*platformclientv2.APIResponse, error) {
org, resp, err := p.organizationApi.GetOrganizationsMe()
if err != nil {
return resp, err
}
// Update org details
*org.DefaultSiteId = siteId
_, resp, err = p.organizationApi.PutOrganizationsMe(*org)
if err != nil {
return resp, fmt.Errorf("error on setting default site. Make sure only one resource has the `set_as_default_site` set to true. %v", err)
}
return resp, nil
}
// getDefaultSiteIdFn is an implementation function for getting the default Site of a Genesys Cloud org
func getDefaultSiteIdFn(ctx context.Context, p *siteProxy) (string, *platformclientv2.APIResponse, error) {
org, resp, err := p.organizationApi.GetOrganizationsMe()
if err != nil {
return "", resp, err
}
return *org.DefaultSiteId, resp, nil
}
package telephony_providers_edges_site
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getSites(ctx context.Context, sdkConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
sp := getSiteProxy(sdkConfig)
unmanagedSites, resp, err := sp.getAllUnmanagedSites(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get unmanaged sites error: %s", err), resp)
}
for _, unmanagedSite := range *unmanagedSites {
resources[*unmanagedSite.Id] = &resourceExporter.ResourceMeta{Name: *unmanagedSite.Name}
}
managedSites, resp, err := sp.getAllManagedSites(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get managed sites error: %s", err), resp)
}
for _, managedSite := range *managedSites {
resources[*managedSite.Id] = &resourceExporter.ResourceMeta{Name: *managedSite.Name}
}
return resources, nil
}
func createSite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
sp := getSiteProxy(sdkConfig)
siteReq := &platformclientv2.Site{
Name: platformclientv2.String(d.Get("name").(string)),
CallerId: platformclientv2.String(d.Get("caller_id").(string)),
CallerName: platformclientv2.String(d.Get("caller_name").(string)),
MediaModel: platformclientv2.String(d.Get("media_model").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
MediaRegionsUseLatencyBased: platformclientv2.Bool(d.Get("media_regions_use_latency_based").(bool)),
}
edgeAutoUpdateConfig, err := buildSdkEdgeAutoUpdateConfig(d)
if err != nil {
return diag.FromErr(err)
}
mediaRegions := lists.BuildSdkStringListFromInterfaceArray(d, "media_regions")
locationId := d.Get("location_id").(string)
location, resp, err := sp.getLocation(ctx, locationId)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get location %s error: %s", locationId, err), resp)
}
err = validateMediaRegions(ctx, sp, mediaRegions)
if err != nil {
return diag.FromErr(err)
}
siteReq.Location = &platformclientv2.Locationdefinition{
Id: platformclientv2.String(locationId),
EmergencyNumber: location.EmergencyNumber,
}
if edgeAutoUpdateConfig != nil {
siteReq.EdgeAutoUpdateConfig = edgeAutoUpdateConfig
}
if mediaRegions != nil {
siteReq.MediaRegions = mediaRegions
}
log.Printf("Creating site %s", *siteReq.Name)
site, resp, err := sp.createSite(ctx, siteReq)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create site %s error: %s", *siteReq.Name, err), resp)
}
d.SetId(*site.Id)
log.Printf("Creating updating site with primary/secondary: %s", *site.Id)
diagErr := updatePrimarySecondarySites(ctx, sp, d, *site.Id)
if diagErr != nil {
return diagErr
}
diagErr = updateSiteNumberPlans(ctx, sp, d)
if diagErr != nil {
return diagErr
}
diagErr = util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
diagErr = updateSiteOutboundRoutes(ctx, sp, d)
if diagErr != nil {
return retry.RetryableError(fmt.Errorf(fmt.Sprintf("%v", diagErr), d.Id()))
}
return nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Created site %s", *site.Id)
// Default site
if d.Get("set_as_default_site").(bool) {
log.Printf("Setting default site to %s", *site.Id)
resp, err := sp.setDefaultSite(ctx, *site.Id)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("unable to set default site to %s error: %s", *site.Id, err), resp)
}
}
return readSite(ctx, d, meta)
}
func readSite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
sp := getSiteProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceSite(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading site %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
currentSite, resp, err := sp.getSiteById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read site %s | error: %s", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read site %s | error: %s", d.Id(), err), resp))
}
d.Set("name", *currentSite.Name)
d.Set("location_id", nil)
if currentSite.Location != nil {
d.Set("location_id", *currentSite.Location.Id)
}
d.Set("media_model", *currentSite.MediaModel)
d.Set("media_regions_use_latency_based", *currentSite.MediaRegionsUseLatencyBased)
resourcedata.SetNillableValue(d, "description", currentSite.Description)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "edge_auto_update_config", currentSite.EdgeAutoUpdateConfig, flattenSdkEdgeAutoUpdateConfig)
resourcedata.SetNillableValue(d, "media_regions", currentSite.MediaRegions)
d.Set("caller_id", currentSite.CallerId)
d.Set("caller_name", currentSite.CallerName)
if currentSite.PrimarySites != nil {
d.Set("primary_sites", util.SdkDomainEntityRefArrToList(*currentSite.PrimarySites))
}
if currentSite.SecondarySites != nil {
d.Set("secondary_sites", util.SdkDomainEntityRefArrToList(*currentSite.SecondarySites))
}
if retryErr := readSiteNumberPlans(ctx, sp, d); retryErr != nil {
return retryErr
}
if retryErr := readSiteOutboundRoutes(ctx, sp, d); retryErr != nil {
return retryErr
}
defaultSiteId, resp, err := sp.getDefaultSiteId(ctx)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get default site id: %v", err), resp))
}
d.Set("set_as_default_site", defaultSiteId == *currentSite.Id)
log.Printf("Read site %s %s", d.Id(), *currentSite.Name)
return cc.CheckState(d)
})
}
func updateSite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
sp := getSiteProxy(sdkConfig)
site := &platformclientv2.Site{
Name: platformclientv2.String(d.Get("name").(string)),
CallerId: platformclientv2.String(d.Get("caller_id").(string)),
CallerName: platformclientv2.String(d.Get("caller_name").(string)),
MediaModel: platformclientv2.String(d.Get("media_model").(string)),
Description: platformclientv2.String(d.Get("description").(string)),
MediaRegionsUseLatencyBased: platformclientv2.Bool(d.Get("media_regions_use_latency_based").(bool)),
}
locationId := d.Get("location_id").(string)
edgeAutoUpdateConfig, err := buildSdkEdgeAutoUpdateConfig(d)
if err != nil {
return diag.FromErr(err)
}
primarySites := lists.InterfaceListToStrings(d.Get("primary_sites").([]interface{}))
secondarySites := lists.InterfaceListToStrings(d.Get("secondary_sites").([]interface{}))
mediaRegions := lists.BuildSdkStringListFromInterfaceArray(d, "media_regions")
location, resp, err := sp.getLocation(ctx, locationId)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get location %s error: %s", locationId, err), resp)
}
site.Location = &platformclientv2.Locationdefinition{
Id: &locationId,
EmergencyNumber: location.EmergencyNumber,
}
err = validateMediaRegions(ctx, sp, mediaRegions)
if err != nil {
return diag.FromErr(err)
}
if edgeAutoUpdateConfig != nil {
site.EdgeAutoUpdateConfig = edgeAutoUpdateConfig
}
if mediaRegions != nil {
site.MediaRegions = mediaRegions
}
if len(primarySites) > 0 {
site.PrimarySites = util.BuildSdkDomainEntityRefArr(d, "primary_sites")
}
if len(secondarySites) > 0 {
site.SecondarySites = util.BuildSdkDomainEntityRefArr(d, "secondary_sites")
}
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// Get current site version
currentSite, resp, err := sp.getSiteById(ctx, d.Id())
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read site %s error: %s", d.Id(), err), resp)
}
site.Version = currentSite.Version
log.Printf("Updating site %s", *site.Name)
site, resp, err = sp.updateSite(ctx, d.Id(), site)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update site %s error: %s", *site.Name, err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
diagErr = updateSiteNumberPlans(ctx, sp, d)
if diagErr != nil {
return diagErr
}
diagErr = updateSiteOutboundRoutes(ctx, sp, d)
if diagErr != nil {
return diagErr
}
if d.Get("set_as_default_site").(bool) {
log.Printf("Setting default site to %s", *site.Id)
resp, err := sp.setDefaultSite(ctx, *site.Id)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to to set default site to %s error: %s", *site.Id, err), resp)
}
}
log.Printf("Updated site %s", *site.Id)
time.Sleep(5 * time.Second)
return readSite(ctx, d, meta)
}
func deleteSite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
sp := getSiteProxy(sdkConfig)
log.Printf("Deleting site")
resp, err := sp.deleteSite(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Site already deleted %s", d.Id())
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete site %s error: %s", d.Id(), err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
site, resp, err := sp.getSiteById(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
// Site deleted
log.Printf("Deleted site %s", d.Id())
// Need to sleep here because if terraform deletes the dependent location straight away
// the API will think it's still in use
time.Sleep(8 * time.Second)
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting site %s | error: %s", d.Id(), err), resp))
}
if site.State != nil && *site.State == "deleted" {
// Site deleted
log.Printf("Deleted site %s", d.Id())
// Need to sleep here because if terraform deletes the dependent location straight away
// the API will think it's still in use
time.Sleep(8 * time.Second)
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("site %s still exists", d.Id()), resp))
})
}
package telephony_providers_edges_site
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
)
/*
resource_genesyscloud_telephony_providers_edges_site_schema.go should hold four types of functions within it:
1. The registration code that registers the Datasource, Resource and Exporter for the package.
2. The resource schema definitions for the telephony_providers_edges_site resource.
3. The datasource schema definitions for the telephony_providers_edges_site datasource.
4. The resource exporter configuration for the telephony_providers_edges_site exporter.
*/
const resourceName = "genesyscloud_telephony_providers_edges_site"
// used in sdk authorization for tests
var (
sdkConfig *platformclientv2.Configuration
authErr error
)
var (
// This is outside the ResourceSite because it is used in a utility function.
outboundRouteSchema = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "The resource's description.",
Type: schema.TypeString,
Optional: true,
},
"classification_types": {
Description: "Used to classify this outbound route.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"enabled": {
Description: "Enable or disable the outbound route",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"distribution": {
Description: "Valid values: SEQUENTIAL, RANDOM.",
Type: schema.TypeString,
Optional: true,
Default: "SEQUENTIAL",
ValidateFunc: validation.StringInSlice([]string{"SEQUENTIAL", "RANDOM"}, false),
},
"external_trunk_base_ids": {
Description: "Trunk base settings of trunkType \"EXTERNAL\". This base must also be set on an edge logical interface for correct routing. The order of the IDs determines the distribution if \"distribution\" is set to \"SEQUENTIAL\"",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
// SetRegistrar registers all of the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceSite())
l.RegisterResource(resourceName, ResourceSite())
l.RegisterExporter(resourceName, SiteExporter())
}
// ResourceSite registers the genesyscloud_telephony_providers_edges_site resource with Terraform
func ResourceSite() *schema.Resource {
edgeAutoUpdateConfigSchema := &schema.Resource{
Schema: map[string]*schema.Schema{
"time_zone": {
Description: "The timezone of the window in which any updates to the edges assigned to the site can be applied. The minimum size of the window is 2 hours.",
Type: schema.TypeString,
Required: true,
},
"rrule": {
Description: "A reoccurring rule for updating the Edges assigned to the site. The only supported frequencies are daily and weekly. Weekly frequencies require a day list with at least oneday specified. All other configurations are not supported.",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateRrule,
},
"start": {
Description: "Date time is represented as an ISO-8601 string without a timezone. For example: yyyy-MM-ddTHH:mm:ss.SSS",
Type: schema.TypeString,
Required: true,
},
"end": {
Description: "Date time is represented as an ISO-8601 string without a timezone. For example: yyyy-MM-ddTHH:mm:ss.SSS",
Type: schema.TypeString,
Required: true,
},
},
}
numberPlansSchema := &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"match_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"digitLength", "e164NumberList", "interCountryCode", "intraCountryCode", "numberList", "regex"}, false),
},
"normalized_format": {
Description: "Use regular expression capture groups to build the normalized number",
Type: schema.TypeString,
Optional: true,
},
"match_format": {
Description: "Use regular expression capture groups to build the normalized number",
Type: schema.TypeString,
Optional: true,
},
"numbers": {
Description: "Numbers must be 2-9 digits long. Numbers within ranges must be the same length. (e.g. 888, 888-999, 55555-77777, 800).",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"start": {
Type: schema.TypeString,
Optional: true,
},
"end": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"digit_length": {
Description: "Allowed values are between 1-20 digits.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"start": {
Type: schema.TypeString,
Optional: true,
},
"end": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"classification": {
Description: "Used to classify this number plan",
Type: schema.TypeString,
Required: true,
},
},
}
return &schema.Resource{
Description: "Genesys Cloud Site",
CreateContext: provider.CreateWithPooledClient(createSite),
ReadContext: provider.ReadWithPooledClient(readSite),
UpdateContext: provider.UpdateWithPooledClient(updateSite),
DeleteContext: provider.DeleteWithPooledClient(deleteSite),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the entity.",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "The resource's description.",
Type: schema.TypeString,
Optional: true,
},
"location_id": {
Description: "Site location ID",
Type: schema.TypeString,
Required: true,
},
"media_model": {
Description: "Media model for the site Valid Values: Premises, Cloud. Changing the media_model attribute will cause the site object to be dropped and created with a new ID.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Premises", "Cloud"}, false),
ForceNew: true,
},
"media_regions_use_latency_based": {
Description: "Latency based on media region",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"media_regions": {
Description: "The ordered list of AWS regions through which media can stream. A full list of available media regions can be found at the GET /api/v2/telephony/mediaregions endpoint",
Type: schema.TypeList, //This has to be a list because it must be ordered
Optional: true,
Computed: true, //This needs to be a computed field because the sites API automatically adds the home region to whatever regions you add add.
Elem: &schema.Schema{Type: schema.TypeString},
},
"caller_id": {
Description: "The caller ID value for the site. The callerID must be a valid E.164 formatted phone number",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: gcloud.ValidatePhoneNumber,
},
"caller_name": {
Description: "The caller name for the site",
Type: schema.TypeString,
Optional: true,
},
"edge_auto_update_config": {
Description: "Recurrence rule, time zone, and start/end settings for automatic edge updates for this site",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: edgeAutoUpdateConfigSchema,
},
"number_plans": {
Description: "Number plans for the site. The order of the plans in the resource file determines the priority of the plans. Specifying number plans will not result in the default plans being overwritten.",
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: numberPlansSchema,
},
"outbound_routes": {
Description: "Outbound Routes for the site. The default outbound route will be deleted if routes are specified",
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: outboundRouteSchema,
},
"primary_sites": {
Description: `Used for primary phone edge assignment on physical edges only. List of primary sites the phones can be assigned to. If no primary_sites are defined, the site id for this site will be used as the primary site id.`,
Optional: true,
Computed: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
"secondary_sites": {
Description: `Used for secondary phone edge assignment on physical edges only. List of secondary sites the phones can be assigned to. If no primary_sites or secondary_sites are defined then the current site will defined as primary and secondary. `,
Optional: true,
Computed: true,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
"set_as_default_site": {
Description: `Set this site as the default site for the organization. Only one genesyscloud_telephony_providers_edges_site resource should be set as the default.`,
Optional: true,
Default: false,
Type: schema.TypeBool,
},
},
CustomizeDiff: customizeSiteDiff,
}
}
// SiteExporter returns the resourceExporter object used to hold the genesyscloud_telephony_providers_edges_site exporter's config
func SiteExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getSites),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"location_id": {RefType: "genesyscloud_location"},
"outbound_routes.external_trunk_base_ids": {RefType: "genesyscloud_telephony_providers_edges_trunkbasesettings"},
"primary_sites": {RefType: "genesyscloud_telephony_providers_edges_site"},
"secondary_sites": {RefType: "genesyscloud_telephony_providers_edges_site"},
},
CustomValidateExports: map[string][]string{
"rrule": {"edge_auto_update_config.rrule"},
},
}
}
// DataSourceSite registers the genesyscloud_telephony_providers_edges_site data source
func DataSourceSite() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Sites. Select a site by name",
ReadContext: provider.ReadWithPooledClient(dataSourceSiteRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Site name.",
Type: schema.TypeString,
Required: true,
},
"managed": {
Description: "Return entities that are managed by Genesys Cloud.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
package telephony_providers_edges_site
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/leekchan/timeutil"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var (
defaultPlans = []string{"Emergency", "Extension", "National", "International", "Network", "Suicide Prevention"}
)
func customizeSiteDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
if diff.HasChange("number_plans") {
oldNumberPlans, newNumberPlans := diff.GetChange("number_plans")
oldNumberPlansList := oldNumberPlans.([]interface{})
newNumberPlansList := newNumberPlans.([]interface{})
if len(oldNumberPlansList) <= len(newNumberPlansList) {
return nil
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
siteId := diff.Id()
if siteId == "" {
return nil
}
numberPlansFromApi, resp, err := edgesAPI.GetTelephonyProvidersEdgesSiteNumberplans(siteId)
if err != nil {
return fmt.Errorf("failed to get number plans from site %s: %s %v", siteId, err, resp)
}
for _, np := range numberPlansFromApi {
if isDefaultPlan(*np.Name) && isNumberPlanInConfig(*np.Name, oldNumberPlansList) && !isNumberPlanInConfig(*np.Name, newNumberPlansList) {
newNumberPlansList = append(newNumberPlansList, flattenNumberPlan(&np))
}
}
for i, x := range newNumberPlansList {
log.Printf("%v: %v", i, x)
}
diff.SetNew("number_plans", newNumberPlansList)
}
return nil
}
func validateMediaRegions(ctx context.Context, sp *siteProxy, regions *[]string) error {
telephonyRegions, _, err := sp.getTelephonyMediaregions(ctx)
if err != nil {
return err
}
homeRegion := telephonyRegions.AwsHomeRegion
coreRegions := telephonyRegions.AwsCoreRegions
satRegions := telephonyRegions.AwsSatelliteRegions
for _, region := range *regions {
if region != *homeRegion &&
!lists.ItemInSlice(region, *coreRegions) &&
!lists.ItemInSlice(region, *satRegions) {
return fmt.Errorf("region %s is not a valid media region. please refer to the Genesys Cloud GET /api/v2/telephony/mediaregions for list of valid regions", regions)
}
}
return nil
}
func nameInPlans(name string, plans []platformclientv2.Numberplan) (*platformclientv2.Numberplan, bool) {
for _, plan := range plans {
if name == *plan.Name {
return &plan, true
}
}
return nil, false
}
func nameInOutboundRoutes(name string, outboundRoutes []platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, bool) {
for _, outboundRoute := range outboundRoutes {
if name == *outboundRoute.Name {
return &outboundRoute, true
}
}
return nil, false
}
// Contains the logic to determine if a primary or secondary site need to be updated.
func updatePrimarySecondarySites(ctx context.Context, sp *siteProxy, d *schema.ResourceData, siteId string) diag.Diagnostics {
primarySites := lists.InterfaceListToStrings(d.Get("primary_sites").([]interface{}))
secondarySites := lists.InterfaceListToStrings(d.Get("secondary_sites").([]interface{}))
site, resp, err := sp.getSiteById(ctx, siteId)
if resp.StatusCode != 200 {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Unable to retrieve site record after site %s was created, but unable to update the primary or secondary site error: %s", siteId, err), resp)
}
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Unable to retrieve site record after site %s was created, but unable to update the primary or secondary siteerror: %s ", siteId, err), resp)
}
if len(primarySites) == 0 && len(secondarySites) > 0 {
der := platformclientv2.Domainentityref{Id: &siteId}
derArr := make([]platformclientv2.Domainentityref, 1)
derArr[0] = der
site.PrimarySites = &derArr
}
if len(primarySites) > 0 {
site.PrimarySites = util.BuildSdkDomainEntityRefArr(d, "primary_sites")
}
if len(secondarySites) > 0 {
site.SecondarySites = util.BuildSdkDomainEntityRefArr(d, "secondary_sites")
}
_, resp, err = sp.updateSite(ctx, siteId, site)
if resp.StatusCode != 200 {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Site %s was created, but unable to update the primary or secondary site. Status code %d. RespBody %s", siteId, resp.StatusCode, resp.RawBody), resp)
}
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Site %s was created, but unable to update the primary or secondary site | error: %s", siteId, err), resp)
}
return nil
}
func updateSiteNumberPlans(ctx context.Context, sp *siteProxy, d *schema.ResourceData) diag.Diagnostics {
if !d.HasChange("number_plans") {
return nil
}
nps := d.Get("number_plans").([]interface{})
if nps == nil {
return nil
}
numberPlansFromTf := make([]platformclientv2.Numberplan, 0)
for _, np := range nps {
npMap := np.(map[string]interface{})
numberPlanFromTf := platformclientv2.Numberplan{}
resourcedata.BuildSDKStringValueIfNotNil(&numberPlanFromTf.Name, npMap, "name")
resourcedata.BuildSDKStringValueIfNotNil(&numberPlanFromTf.MatchType, npMap, "match_type")
resourcedata.BuildSDKStringValueIfNotNil(&numberPlanFromTf.Match, npMap, "match_format")
resourcedata.BuildSDKStringValueIfNotNil(&numberPlanFromTf.NormalizedFormat, npMap, "normalized_format")
resourcedata.BuildSDKStringValueIfNotNil(&numberPlanFromTf.Classification, npMap, "classification")
if numbers, ok := npMap["numbers"].([]interface{}); ok && len(numbers) > 0 {
sdkNumbers := make([]platformclientv2.Number, 0)
for _, number := range numbers {
numberMap := number.(map[string]interface{})
sdkNumber := platformclientv2.Number{}
if start, ok := numberMap["start"].(string); ok {
sdkNumber.Start = &start
}
if end, ok := numberMap["end"].(string); ok {
sdkNumber.End = &end
}
sdkNumbers = append(sdkNumbers, sdkNumber)
}
numberPlanFromTf.Numbers = &sdkNumbers
}
if digitLength, ok := npMap["digit_length"].([]interface{}); ok && len(digitLength) > 0 {
sdkDigitlengthMap := digitLength[0].(map[string]interface{})
sdkDigitlength := platformclientv2.Digitlength{}
if start, ok := sdkDigitlengthMap["start"].(string); ok {
sdkDigitlength.Start = &start
}
if end, ok := sdkDigitlengthMap["end"].(string); ok {
sdkDigitlength.End = &end
}
numberPlanFromTf.DigitLength = &sdkDigitlength
}
numberPlansFromTf = append(numberPlansFromTf, numberPlanFromTf)
}
// The default plans won't be assigned yet if there isn't a wait
time.Sleep(5 * time.Second)
numberPlansFromAPI, resp, err := sp.getSiteNumberPlans(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get number plans for site %s error: %s", d.Id(), err), resp)
}
updatedNumberPlans := make([]platformclientv2.Numberplan, 0)
namesOfOverridenDefaults := []string{}
for _, numberPlanFromTf := range numberPlansFromTf {
if plan, ok := nameInPlans(*numberPlanFromTf.Name, *numberPlansFromAPI); ok {
// Update the plan
plan.Classification = numberPlanFromTf.Classification
plan.Numbers = numberPlanFromTf.Numbers
plan.DigitLength = numberPlanFromTf.DigitLength
plan.Match = numberPlanFromTf.Match
plan.MatchType = numberPlanFromTf.MatchType
plan.NormalizedFormat = numberPlanFromTf.NormalizedFormat
namesOfOverridenDefaults = append(namesOfOverridenDefaults, *numberPlanFromTf.Name)
updatedNumberPlans = append(updatedNumberPlans, *plan)
} else {
// Add the plan
updatedNumberPlans = append(updatedNumberPlans, numberPlanFromTf)
}
}
for _, numberPlanFromAPI := range *numberPlansFromAPI {
// Keep the default plans which are not overriden.
if isDefaultPlan(*numberPlanFromAPI.Name) && !lists.ItemInSlice(*numberPlanFromAPI.Name, namesOfOverridenDefaults) {
updatedNumberPlans = append(updatedNumberPlans, numberPlanFromAPI)
}
}
diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
log.Printf("Updating number plans for site %s", d.Id())
_, resp, err := sp.updateSiteNumberPlans(ctx, d.Id(), &updatedNumberPlans)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update number plans for site %s | error: %s", d.Id(), err), resp)
}
return resp, nil
})
if diagErr != nil {
return diagErr
}
// Wait for the update before reading
time.Sleep(5 * time.Second)
return nil
}
func updateSiteOutboundRoutes(ctx context.Context, sp *siteProxy, d *schema.ResourceData) diag.Diagnostics {
if !d.HasChange("outbound_routes") {
return nil
}
ors := d.Get("outbound_routes").(*schema.Set)
if ors == nil {
return nil
}
orsList := ors.List()
outboundRoutesFromTf := make([]platformclientv2.Outboundroutebase, 0)
for _, or := range orsList {
orMap := or.(map[string]interface{})
outboundRouteFromTf := platformclientv2.Outboundroutebase{}
resourcedata.BuildSDKStringValueIfNotNil(&outboundRouteFromTf.Name, orMap, "name")
resourcedata.BuildSDKStringValueIfNotNil(&outboundRouteFromTf.Description, orMap, "description")
if classificationTypes, ok := orMap["classification_types"].([]interface{}); ok && len(classificationTypes) > 0 {
cts := make([]string, 0)
for _, classificationType := range classificationTypes {
cts = append(cts, classificationType.(string))
}
outboundRouteFromTf.ClassificationTypes = &cts
}
if enabled, ok := orMap["enabled"].(bool); ok {
outboundRouteFromTf.Enabled = &enabled
}
resourcedata.BuildSDKStringValueIfNotNil(&outboundRouteFromTf.Distribution, orMap, "distribution")
if externalTrunkBaseIds, ok := orMap["external_trunk_base_ids"].([]interface{}); ok && len(externalTrunkBaseIds) > 0 {
ids := make([]platformclientv2.Domainentityref, 0)
for _, externalTrunkBaseId := range externalTrunkBaseIds {
externalTrunkBaseIdStr := externalTrunkBaseId.(string)
ids = append(ids, platformclientv2.Domainentityref{Id: &externalTrunkBaseIdStr})
}
outboundRouteFromTf.ExternalTrunkBases = &ids
}
outboundRoutesFromTf = append(outboundRoutesFromTf, outboundRouteFromTf)
}
// The default outbound routes won't be assigned yet if there isn't a wait
time.Sleep(5 * time.Second)
// Get the current outbound routes
outboundRoutesFromAPI, resp, err := sp.getSiteOutboundRoutes(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get outbound routes for site %s error: %s", d.Id(), err), resp)
}
// Delete unwanted outbound roues first to free up classifications assigned to them
for _, outboundRouteFromAPI := range *outboundRoutesFromAPI {
// Delete route if no reference to it
if _, ok := nameInOutboundRoutes(*outboundRouteFromAPI.Name, outboundRoutesFromTf); !ok {
resp, err := sp.deleteSiteOutboundRoute(ctx, d.Id(), *outboundRouteFromAPI.Id)
if err != nil {
if util.IsStatus404(resp) {
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete outbound route from site %s error: %s", d.Id(), err), resp)
}
}
}
time.Sleep(2 * time.Second)
// Update the outbound routes
for _, outboundRouteFromTf := range outboundRoutesFromTf {
if outboundRoute, ok := nameInOutboundRoutes(*outboundRouteFromTf.Name, *outboundRoutesFromAPI); ok {
// Update the outbound route
outboundRoute.Name = outboundRouteFromTf.Name
outboundRoute.Description = outboundRouteFromTf.Description
outboundRoute.ClassificationTypes = outboundRouteFromTf.ClassificationTypes
outboundRoute.Enabled = outboundRouteFromTf.Enabled
outboundRoute.Distribution = outboundRouteFromTf.Distribution
outboundRoute.ExternalTrunkBases = outboundRouteFromTf.ExternalTrunkBases
_, resp, err := sp.updateSiteOutboundRoute(ctx, d.Id(), *outboundRoute.Id, outboundRoute)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update outbound route with id %s for site %s error: %s", *outboundRoute.Id, d.Id(), err), resp)
}
} else {
// Add the outbound route
_, resp, err := sp.createSiteOutboundRoute(ctx, d.Id(), &outboundRouteFromTf)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to add outbound route to site %s error: %s", d.Id(), err), resp)
}
}
}
// Wait for the update before reading
time.Sleep(5 * time.Second)
return nil
}
func isDefaultPlan(name string) bool {
for _, defaultPlan := range defaultPlans {
if name == defaultPlan {
return true
}
}
return false
}
// isNumberPlanInConfig returns true if the number plan's name is in the config list
func isNumberPlanInConfig(planName string, list []interface{}) bool {
for _, plan := range list {
planMap := plan.(map[string]interface{})
if planName == planMap["name"] {
return true
}
}
return false
}
func readSiteNumberPlans(ctx context.Context, sp *siteProxy, d *schema.ResourceData) *retry.RetryError {
numberPlans, resp, err := sp.getSiteNumberPlans(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
d.SetId("") // Site doesn't exist
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read number plans for site %s | error: %s", d.Id(), err), resp))
}
dNumberPlans := make([]interface{}, 0)
if len(*numberPlans) > 0 {
for _, numberPlan := range *numberPlans {
dNumberPlan := flattenNumberPlan(&numberPlan)
dNumberPlans = append(dNumberPlans, dNumberPlan)
}
d.Set("number_plans", dNumberPlans)
} else {
d.Set("number_plans", nil)
}
return nil
}
func readSiteOutboundRoutes(ctx context.Context, sp *siteProxy, d *schema.ResourceData) *retry.RetryError {
outboundRoutes, resp, err := sp.getSiteOutboundRoutes(ctx, d.Id())
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get outbound routes for site %s | error: %s", d.Id(), err), resp))
}
dOutboundRoutes := schema.NewSet(schema.HashResource(outboundRouteSchema), []interface{}{})
if len(*outboundRoutes) > 0 {
for _, outboundRoute := range *outboundRoutes {
dOutboundRoute := make(map[string]interface{})
dOutboundRoute["name"] = *outboundRoute.Name
resourcedata.SetMapValueIfNotNil(dOutboundRoute, "description", outboundRoute.Description)
resourcedata.SetMapValueIfNotNil(dOutboundRoute, "enabled", outboundRoute.Enabled)
resourcedata.SetMapValueIfNotNil(dOutboundRoute, "distribution", outboundRoute.Distribution)
if outboundRoute.ClassificationTypes != nil {
dOutboundRoute["classification_types"] = lists.StringListToInterfaceList(*outboundRoute.ClassificationTypes)
}
if len(*outboundRoute.ExternalTrunkBases) > 0 {
externalTrunkBaseIds := make([]string, 0)
for _, externalTrunkBase := range *outboundRoute.ExternalTrunkBases {
externalTrunkBaseIds = append(externalTrunkBaseIds, *externalTrunkBase.Id)
}
dOutboundRoute["external_trunk_base_ids"] = lists.StringListToInterfaceList(externalTrunkBaseIds)
}
dOutboundRoutes.Add(dOutboundRoute)
}
d.Set("outbound_routes", dOutboundRoutes)
} else {
d.Set("outbound_routes", nil)
}
return nil
}
func flattenSdkEdgeAutoUpdateConfig(edgeAutoUpdateConfig *platformclientv2.Edgeautoupdateconfig) []interface{} {
if edgeAutoUpdateConfig == nil {
return nil
}
edgeAutoUpdateConfigMap := make(map[string]interface{})
edgeAutoUpdateConfigMap["time_zone"] = *edgeAutoUpdateConfig.TimeZone
edgeAutoUpdateConfigMap["rrule"] = *edgeAutoUpdateConfig.Rrule
edgeAutoUpdateConfigMap["start"] = timeutil.Strftime(edgeAutoUpdateConfig.Start, "%Y-%m-%dT%H:%M:%S.%f")
edgeAutoUpdateConfigMap["end"] = timeutil.Strftime(edgeAutoUpdateConfig.End, "%Y-%m-%dT%H:%M:%S.%f")
return []interface{}{edgeAutoUpdateConfigMap}
}
func flattenNumberPlan(numberPlan *platformclientv2.Numberplan) interface{} {
dNumberPlan := make(map[string]interface{})
dNumberPlan["name"] = *numberPlan.Name
resourcedata.SetMapValueIfNotNil(dNumberPlan, "match_format", numberPlan.Match)
resourcedata.SetMapValueIfNotNil(dNumberPlan, "normalized_format", numberPlan.NormalizedFormat)
resourcedata.SetMapValueIfNotNil(dNumberPlan, "classification", numberPlan.Classification)
resourcedata.SetMapValueIfNotNil(dNumberPlan, "match_type", numberPlan.MatchType)
if numberPlan.Numbers != nil {
numbers := make([]interface{}, 0)
for _, number := range *numberPlan.Numbers {
numberMap := make(map[string]interface{})
if number.Start != nil {
numberMap["start"] = *number.Start
}
if number.End != nil {
numberMap["end"] = *number.End
}
numbers = append(numbers, numberMap)
}
dNumberPlan["numbers"] = numbers
}
if numberPlan.DigitLength != nil {
digitLength := make([]interface{}, 0)
digitLengthMap := make(map[string]interface{})
if numberPlan.DigitLength.Start != nil {
digitLengthMap["start"] = *numberPlan.DigitLength.Start
}
if numberPlan.DigitLength.End != nil {
digitLengthMap["end"] = *numberPlan.DigitLength.End
}
digitLength = append(digitLength, digitLengthMap)
dNumberPlan["digit_length"] = digitLength
}
return dNumberPlan
}
func buildSdkEdgeAutoUpdateConfig(d *schema.ResourceData) (*platformclientv2.Edgeautoupdateconfig, error) {
if edgeAutoUpdateConfig := d.Get("edge_auto_update_config"); edgeAutoUpdateConfig != nil {
if edgeAutoUpdateConfigList := edgeAutoUpdateConfig.([]interface{}); len(edgeAutoUpdateConfigList) > 0 {
edgeAutoUpdateConfigMap := edgeAutoUpdateConfigList[0].(map[string]interface{})
timeZone := edgeAutoUpdateConfigMap["time_zone"].(string)
rrule := edgeAutoUpdateConfigMap["rrule"].(string)
startStr := edgeAutoUpdateConfigMap["start"].(string)
endStr := edgeAutoUpdateConfigMap["end"].(string)
start, err := time.Parse("2006-01-02T15:04:05.000000", startStr)
if err != nil {
return nil, fmt.Errorf("failed to parse date %s: %s", startStr, err)
}
end, err := time.Parse("2006-01-02T15:04:05.000000", endStr)
if err != nil {
return nil, fmt.Errorf("failed to parse date %s: %s", end, err)
}
return &platformclientv2.Edgeautoupdateconfig{
TimeZone: &timeZone,
Rrule: &rrule,
Start: &start,
End: &end,
}, nil
}
}
return nil, nil
}
func GenerateSiteResourceWithCustomAttrs(
siteRes,
name,
description,
locationId,
mediaModel string,
mediaRegionsUseLatencyBased bool,
mediaRegions string,
callerId string,
callerName string,
otherAttrs ...string) string {
return fmt.Sprintf(`resource "genesyscloud_telephony_providers_edges_site" "%s" {
name = "%s"
description = "%s"
location_id = %s
media_model = "%s"
media_regions_use_latency_based = %v
media_regions= %s
caller_id = %s
caller_name = %s
%s
}
`, siteRes, name, description, locationId, mediaModel, mediaRegionsUseLatencyBased, mediaRegions, callerId, callerName, strings.Join(otherAttrs, "\n"))
}
// DeleteLocationWithNumber is a test utility function to delete site and location with the provided emergency number
func DeleteLocationWithNumber(emergencyNumber string, config *platformclientv2.Configuration) error {
var (
locationsAPI = platformclientv2.NewLocationsApiWithConfig(config)
pageCount int
)
const pageSize = 100
log.Printf("Reading locations")
locations, _, getErr := locationsAPI.GetLocations(pageSize, 1, []string{}, "")
if getErr != nil {
return getErr
}
log.Printf("Read locations")
if locations.Entities == nil || len(*locations.Entities) == 0 {
return nil
}
pageCount = *locations.PageCount
for pageNum := 1; pageNum <= pageCount; pageNum++ {
const pageSize = 100
log.Printf("Reading locations")
locations, _, getErr := locationsAPI.GetLocations(pageSize, pageNum, []string{}, "")
if getErr != nil {
return getErr
}
log.Printf("Read locations")
if locations.Entities == nil || len(*locations.Entities) == 0 {
break
}
for _, location := range *locations.Entities {
if location.EmergencyNumber != nil {
if location.EmergencyNumber.E164 == nil {
continue
}
if strings.Contains(*location.EmergencyNumber.E164, emergencyNumber) {
err := deleteSiteWithLocationId(*location.Id, config)
if err != nil {
return err
}
log.Printf("Deleting location %s", *location.Id)
if _, err = locationsAPI.DeleteLocation(*location.Id); err != nil {
return err
}
log.Printf("Deleted location %s", *location.Id)
time.Sleep(30 * time.Second)
return nil
}
}
}
}
return nil
}
// deleteSiteWithLocationId is a test utility function that will
// delete a site with the provided location id
func deleteSiteWithLocationId(locationId string, config *platformclientv2.Configuration) error {
const pageSize = 100
var (
pageCount int
edgesAPI = platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(config)
)
log.Printf("Reading telephony providers edges sites with location ID %s", locationId)
sites, _, getErr := edgesAPI.GetTelephonyProvidersEdgesSites(pageSize, 1, "", "", "", locationId, false, nil)
if getErr != nil {
return getErr
}
log.Printf("Read telephony providers edges sites with location ID %s", locationId)
if sites.Entities == nil || len(*sites.Entities) == 0 {
return nil
}
pageCount = *sites.PageCount
for pageNum := 1; pageNum <= pageCount; pageNum++ {
log.Printf("Reading telephony providers edges site with location ID %s", locationId)
sites, _, getErr = edgesAPI.GetTelephonyProvidersEdgesSites(pageSize, pageNum, "", "", "", locationId, false, nil)
if getErr != nil {
return getErr
}
log.Printf("Read telephony providers edges sites with location ID %s", locationId)
if sites.Entities == nil || len(*sites.Entities) == 0 {
return nil
}
for _, site := range *sites.Entities {
if site.Location != nil && *site.Location.Id == locationId {
log.Printf("Deleting telephony providers edges site %s", *site.Id)
if _, err := edgesAPI.DeleteTelephonyProvidersEdgesSite(*site.Id); err != nil {
return err
}
log.Printf("Deleted telephony providers edges site %s", *site.Id)
time.Sleep(8 * time.Second)
}
}
}
return nil
}
// GetOrganizationDefaultSiteId is a test utiliy function to get the default site ID of the org
func GetOrganizationDefaultSiteId(config *platformclientv2.Configuration) (siteId string, err error) {
organizationApi := platformclientv2.NewOrganizationApiWithConfig(config)
org, _, err := organizationApi.GetOrganizationsMe()
if err != nil {
return "", err
}
if org.DefaultSiteId == nil {
return "", nil
}
return *org.DefaultSiteId, nil
}
package telephony_providers_edges_trunk
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func dataSourceTrunkRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
trunks, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesTrunks(pageNum, pageSize, "", "", "", "", "")
if getErr != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting trunk %s | error: %s", name, getErr), resp))
}
if trunks.Entities == nil || len(*trunks.Entities) == 0 {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No trunk found with name %s", name), resp))
}
for _, trunk := range *trunks.Entities {
if trunk.Name != nil && *trunk.Name == name &&
trunk.State != nil && *trunk.State != "deleted" {
d.SetId(*trunk.Id)
return nil
}
}
}
})
}
package telephony_providers_edges_trunk
import (
"context"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
//generate a proxy for telephony_providers_edges_trunk
// internalProxy holds a proxy instance that can be used throughout the package
var internalProxy *trunkProxy
// Type definitions for each func on our proxy so we can easily mock them out later
type getTrunkByIdFunc func(ctx context.Context, p *trunkProxy, id string) (*platformclientv2.Trunk, *platformclientv2.APIResponse, error)
type getAllTrunksFunc func(ctx context.Context, p *trunkProxy, pageNum int, pageSize int) (*platformclientv2.Trunkentitylisting, *platformclientv2.APIResponse, error)
type getTrunkBaseSettingsFunc func(ctx context.Context, p *trunkProxy, trunkBaseSettingsId string) (*platformclientv2.Trunkbase, *platformclientv2.APIResponse, error)
type getEdgeFunc func(ctx context.Context, p *trunkProxy, edgeId string) (*platformclientv2.Edge, *platformclientv2.APIResponse, error)
type putEdgeFunc func(ctx context.Context, p *trunkProxy, edgeId string, edge platformclientv2.Edge) (*platformclientv2.Edge, *platformclientv2.APIResponse, error)
type getEdgeGroupFunc func(ctx context.Context, p *trunkProxy, edgeGroupId string) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error)
type putEdgeGroupFunc func(ctx context.Context, p *trunkProxy, edgeGroupId string, edgeGroup platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error)
// Proxy contains all of the methods that call genesys cloud APIs.
type trunkProxy struct {
clientConfig *platformclientv2.Configuration
edgesApi *platformclientv2.TelephonyProvidersEdgeApi
getTrunkByIdAttr getTrunkByIdFunc
getAllTrunksAttr getAllTrunksFunc
getTrunkBaseSettingsAttr getTrunkBaseSettingsFunc
getEdgeAttr getEdgeFunc
putEdgeAttr putEdgeFunc
getEdgeGroupAttr getEdgeGroupFunc
putEdgeGroupAttr putEdgeGroupFunc
}
// initializes the proxy with all of the data needed to communicate with Genesys Cloud
func newTrunkProxy(clientConfig *platformclientv2.Configuration) *trunkProxy {
edgesApi := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(clientConfig)
return &trunkProxy{
clientConfig: clientConfig,
edgesApi: edgesApi,
getTrunkByIdAttr: getTrunkByIdFn,
getAllTrunksAttr: getAllTrunksFn,
getEdgeAttr: getEdgeFn,
putEdgeAttr: putEdgeFn,
getEdgeGroupAttr: getEdgeGroupFn,
putEdgeGroupAttr: putEdgeGroupFn,
getTrunkBaseSettingsAttr: getTrunkBaseSettingsFn,
}
}
// getTeamProxy acts as a singleton to for the internalProxy. It also ensures
// that we can still proxy our tests by directly setting internalProxy package variable
func getTrunkProxy(clientConfig *platformclientv2.Configuration) *trunkProxy {
if internalProxy == nil {
internalProxy = newTrunkProxy(clientConfig)
}
return internalProxy
}
func (p *trunkProxy) getEdge(ctx context.Context, edgeId string) (*platformclientv2.Edge, *platformclientv2.APIResponse, error) {
return p.getEdgeAttr(ctx, p, edgeId)
}
func (p *trunkProxy) putEdge(ctx context.Context, edgeId string, edge platformclientv2.Edge) (*platformclientv2.Edge, *platformclientv2.APIResponse, error) {
return p.putEdgeAttr(ctx, p, edgeId, edge)
}
func (p *trunkProxy) getEdgeGroup(ctx context.Context, edgeGroupId string) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.getEdgeGroupAttr(ctx, p, edgeGroupId)
}
func (p *trunkProxy) putEdgeGroup(ctx context.Context, edgeGroupId string, edgeGroup platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.putEdgeGroupAttr(ctx, p, edgeGroupId, edgeGroup)
}
func (p *trunkProxy) getTrunkBaseSettings(ctx context.Context, trunkBaseSettingsId string) (*platformclientv2.Trunkbase, *platformclientv2.APIResponse, error) {
return p.getTrunkBaseSettingsAttr(ctx, p, trunkBaseSettingsId)
}
func (p *trunkProxy) getTrunkById(ctx context.Context, id string) (*platformclientv2.Trunk, *platformclientv2.APIResponse, error) {
return p.getTrunkByIdAttr(ctx, p, id)
}
func (p *trunkProxy) getAllTrunks(ctx context.Context, pageNum int, pageSize int) (*platformclientv2.Trunkentitylisting, *platformclientv2.APIResponse, error) {
return p.getAllTrunksAttr(ctx, p, pageNum, pageSize)
}
func getEdgeFn(ctx context.Context, p *trunkProxy, edgeId string) (*platformclientv2.Edge, *platformclientv2.APIResponse, error) {
return p.edgesApi.GetTelephonyProvidersEdge(edgeId, nil)
}
func putEdgeFn(ctx context.Context, p *trunkProxy, edgeId string, edge platformclientv2.Edge) (*platformclientv2.Edge, *platformclientv2.APIResponse, error) {
return p.edgesApi.PutTelephonyProvidersEdge(edgeId, edge)
}
func getEdgeGroupFn(ctx context.Context, p *trunkProxy, edgeGroupId string) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.edgesApi.GetTelephonyProvidersEdgesEdgegroup(edgeGroupId, nil)
}
func putEdgeGroupFn(ctx context.Context, p *trunkProxy, edgeGroupId string, edgeGroup platformclientv2.Edgegroup) (*platformclientv2.Edgegroup, *platformclientv2.APIResponse, error) {
return p.edgesApi.PutTelephonyProvidersEdgesEdgegroup(edgeGroupId, edgeGroup)
}
func getTrunkBaseSettingsFn(ctx context.Context, p *trunkProxy, trunkBaseSettingsId string) (*platformclientv2.Trunkbase, *platformclientv2.APIResponse, error) {
return p.edgesApi.GetTelephonyProvidersEdgesTrunkbasesetting(trunkBaseSettingsId, true)
}
func getTrunkByIdFn(ctx context.Context, p *trunkProxy, trunkBaseSettingsId string) (*platformclientv2.Trunk, *platformclientv2.APIResponse, error) {
return p.edgesApi.GetTelephonyProvidersEdgesTrunk(trunkBaseSettingsId)
}
func getAllTrunksFn(ctx context.Context, p *trunkProxy, pageNum int, pageSize int) (*platformclientv2.Trunkentitylisting, *platformclientv2.APIResponse, error) {
return p.edgesApi.GetTelephonyProvidersEdgesTrunks(pageNum, pageSize, "", "", "", "", "")
}
package telephony_providers_edges_trunk
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_telephony_providers_edges_trunk"
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceTrunk())
l.RegisterResource(resourceName, ResourceTrunk())
l.RegisterExporter(resourceName, TrunkExporter())
}
func DataSourceTrunk() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Trunk. Select a trunk by name",
ReadContext: provider.ReadWithPooledClient(dataSourceTrunkRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "Trunk name.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func ResourceTrunk() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Trunk. Created by assigning a trunk base settings to an edge or edge group",
CreateContext: provider.CreateWithPooledClient(createTrunk),
ReadContext: provider.ReadWithPooledClient(readTrunk),
UpdateContext: provider.UpdateWithPooledClient(updateTrunk),
DeleteContext: provider.DeleteWithPooledClient(deleteTrunk),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"trunk_base_settings_id": {
Description: "The trunk base settings reference",
Type: schema.TypeString,
Optional: true,
},
"edge_group_id": {
Description: "The edge group associated with this trunk. Either this or \"edge_id\" must be set",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"edge_id": {
Description: "The edge associated with this trunk. Either this or \"edge_group_id\" must be set",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"name": {
Description: "The name of the trunk. This property is read only and populated with the auto generated name.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
package telephony_providers_edges_trunk
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func createTrunk(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
trunkBaseSettingsId := d.Get("trunk_base_settings_id").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
tp := getTrunkProxy(sdkConfig)
trunkBase, resp, getErr := tp.getTrunkBaseSettings(ctx, trunkBaseSettingsId)
if getErr != nil {
if util.IsStatus404(resp) {
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read trunk base settings %s error: %s", d.Id(), getErr), resp)
}
// Assign to edge if edge_id is set
if edgeIdI, ok := d.GetOk("edge_id"); ok {
edgeId := edgeIdI.(string)
edge, resp, getErr := tp.getEdge(ctx, edgeId)
if getErr != nil {
if util.IsStatus404(resp) {
return nil
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read edge %s error: %s", edgeId, getErr), resp)
}
if edge.EdgeGroup == nil {
edge.EdgeGroup = &platformclientv2.Edgegroup{}
}
edge.EdgeGroup.EdgeTrunkBaseAssignment = &platformclientv2.Trunkbaseassignment{
TrunkBase: trunkBase,
}
log.Printf("Assigning trunk base settings to edge %s", edgeId)
_, resp, err := tp.putEdge(ctx, edgeId, *edge)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to assign trunk base settings to edge %s error: %s", edgeId, err), resp)
}
} else if edgeGroupIdI, ok := d.GetOk("edge_group_id"); ok {
edgeGroupId := edgeGroupIdI.(string)
edgeGroup, resp, getErr := tp.getEdgeGroup(ctx, edgeGroupId)
if getErr != nil {
if util.IsStatus404(resp) {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get edge group %s error: %s", edgeGroupId, getErr), resp)
}
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read edge group %s error: %s", edgeGroupId, getErr), resp)
}
edgeGroup.EdgeTrunkBaseAssignment = &platformclientv2.Trunkbaseassignment{
TrunkBase: trunkBase,
}
log.Printf("Assigning trunk base settings to edge group %s", edgeGroupId)
_, resp, err := tp.putEdgeGroup(ctx, edgeGroupId, *edgeGroup)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to assign trunk base settings to edge group %s error: %s", edgeGroupId, err), resp)
}
} else {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("edge_id or edge_group_id were not set. One must be set in order to assign the trunk base settings"), fmt.Errorf("edge_id or edge_group_id were not set"))
}
trunk, resp, err := getTrunkByTrunkBaseId(ctx, trunkBaseSettingsId, meta)
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get trunk by trunk base id %s error: %s", trunkBaseSettingsId, err), resp)
}
d.SetId(*trunk.Id)
log.Printf("Created trunk %s", *trunk.Id)
return readTrunk(ctx, d, meta)
}
func getTrunkByTrunkBaseId(ctx context.Context, trunkBaseId string, meta interface{}) (*platformclientv2.Trunk, *platformclientv2.APIResponse, error) {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
tp := getTrunkProxy(sdkConfig)
var response *platformclientv2.APIResponse
time.Sleep(2 * time.Second)
// It should return the trunk as the first object. Paginating to be safe
for pageNum := 1; ; pageNum++ {
const pageSize = 100
trunks, resp, getErr := tp.getAllTrunks(ctx, pageNum, pageSize)
response = resp
if getErr != nil {
return nil, resp, fmt.Errorf("Failed to get page of trunks: %v %v", getErr, resp)
}
if trunks.Entities == nil || len(*trunks.Entities) == 0 {
break
}
for _, trunk := range *trunks.Entities {
if *trunk.TrunkBase.Id == trunkBaseId {
return &trunk, resp, nil
}
}
}
return nil, response, fmt.Errorf("Could not find trunk for trunk base setting id: %v", trunkBaseId)
}
func updateTrunk(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return createTrunk(ctx, d, meta)
}
func readTrunk(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
tp := getTrunkProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTrunk(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading trunk %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
trunk, resp, getErr := tp.getTrunkById(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read trunk %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read trunk %s | error: %s", d.Id(), getErr), resp))
}
d.Set("name", *trunk.Name)
if trunk.TrunkBase != nil {
d.Set("trunk_base_settings_id", *trunk.TrunkBase.Id)
}
if trunk.EdgeGroup != nil {
d.Set("edge_group_id", *trunk.EdgeGroup.Id)
}
if trunk.Edge != nil {
d.Set("edge_id", *trunk.Edge.Id)
}
log.Printf("Read trunk %s %s", d.Id(), *trunk.Name)
return cc.CheckState(d)
})
}
func deleteTrunk(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
// Does not delete the trunk. This resource will just no longer manage trunks.
return nil
}
func TrunkExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllTrunks),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"trunk_base_settings_id": {RefType: "genesyscloud_telephony_providers_edges_trunkbasesettings"},
"edge_group_id": {RefType: "genesyscloud_telephony_providers_edges_edge_group"},
},
UnResolvableAttributes: map[string]*schema.Schema{
"edge_id": ResourceTrunk().Schema["edge_id"],
},
}
}
func getAllTrunks(ctx context.Context, sdkConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
tp := getTrunkProxy(sdkConfig)
err := util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
for pageNum := 1; ; pageNum++ {
const pageSize = 100
trunks, resp, getErr := tp.getAllTrunks(ctx, pageNum, pageSize)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of trunks: %v", getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of trunks: %v", getErr), resp))
}
if trunks.Entities == nil || len(*trunks.Entities) == 0 {
break
}
for _, trunk := range *trunks.Entities {
if trunk.State != nil && *trunk.State != "deleted" {
resources[*trunk.Id] = &resourceExporter.ResourceMeta{Name: *trunk.Name}
}
}
}
return nil
})
return resources, err
}
package telephony_providers_edges_trunk
import "fmt"
func generateTrunkDataSource(
resourceID string,
name string,
// Must explicitly use depends_on in terraform v0.13 when a data source references a resource
// Fixed in v0.14 https://github.com/hashicorp/terraform/pull/26284
dependsOnResource string) string {
return fmt.Sprintf(`data "%s" "%s" {
name = %s
depends_on=[%s]
}
`, resourceName, resourceID, name, dependsOnResource)
}
func generateTrunk(
trunkRes,
trunkBaseSettingsId,
edgeGroupId string) string {
return fmt.Sprintf(`resource "%s" "%s" {
trunk_base_settings_id = %s
edge_group_id = %s
}
`, resourceName, trunkRes, trunkBaseSettingsId, edgeGroupId)
}
package tfexporter
var DataSourceExports []string
func SetDataSourceExports() []string {
if len(DataSourceExports) < 1 {
DataSourceExports = append(DataSourceExports, "")
}
return DataSourceExports
}
package tfexporter
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
defaultTfJSONFile = "genesyscloud.tf.json"
defaultTfHCLFile = "genesyscloud.tf"
defaultTfHCLProviderFile = "provider.tf"
defaultTfJSONProviderFile = "provider.tf.json"
defaultTfHCLVariablesFile = "variables.tf"
defaultTfJSONVariablesFile = "variables.tf.json"
defaultTfVarsFile = "terraform.tfvars"
defaultTfStateFile = "terraform.tfstate"
)
// Common Exporter interface to abstract away whether we are using HCL or JSON as our exporter
type Exporter func() diag.Diagnostics
type ExporterFilterType int64
type ExporterResourceTypeFilter func(exports map[string]*resourceExporter.ResourceExporter, filter []string) map[string]*resourceExporter.ResourceExporter
type ExporterResourceFilter func(result resourceExporter.ResourceIDMetaMap, name string, filter []string) resourceExporter.ResourceIDMetaMap
const (
LegacyInclude ExporterFilterType = iota
IncludeResources
ExcludeResources
)
func IncludeFilterByResourceType(exports map[string]*resourceExporter.ResourceExporter, filter []string) map[string]*resourceExporter.ResourceExporter {
if len(filter) > 0 {
for resType := range exports {
if !lists.ItemInSlice(resType, formatFilter(filter)) {
delete(exports, resType)
}
}
}
return exports
}
func ExcludeFilterByResourceType(exports map[string]*resourceExporter.ResourceExporter, filter []string) map[string]*resourceExporter.ResourceExporter {
if len(filter) > 0 {
for resType := range exports {
for _, f := range filter {
if resType == f {
delete(exports, resType)
}
}
}
}
return exports
}
func FilterResourceByName(result resourceExporter.ResourceIDMetaMap, name string, filter []string) resourceExporter.ResourceIDMetaMap {
if lists.SubStringInSlice(fmt.Sprintf("%v::", name), filter) {
names := make([]string, 0)
for _, f := range filter {
n := fmt.Sprintf("%v::", name)
if strings.Contains(f, n) {
names = append(names, strings.Replace(f, n, "", 1))
}
}
newResult := make(resourceExporter.ResourceIDMetaMap)
for _, name := range names {
for k, v := range result {
if v.Name == name {
newResult[k] = v
}
}
}
return newResult
}
return result
}
func FilterResourceById(result resourceExporter.ResourceIDMetaMap, name string, filter []string) resourceExporter.ResourceIDMetaMap {
if lists.SubStringInSlice(fmt.Sprintf("%v::", name), filter) {
names := make([]string, 0)
for _, f := range filter {
n := fmt.Sprintf("%v::", name)
if strings.Contains(f, n) {
names = append(names, strings.Replace(f, n, "", 1))
}
}
newResult := make(resourceExporter.ResourceIDMetaMap)
for _, name := range names {
for k, v := range result {
if k == name {
newResult[k] = v
}
}
}
return newResult
}
return result
}
func IncludeFilterResourceByRegex(result resourceExporter.ResourceIDMetaMap, name string, filter []string) resourceExporter.ResourceIDMetaMap {
newFilters := make([]string, 0)
for _, f := range filter {
if strings.Contains(f, "::") && strings.Split(f, "::")[0] == name {
i := strings.Index(f, "::")
regexStr := f[i+2:]
newFilters = append(newFilters, regexStr)
}
}
newResourceMap := make(resourceExporter.ResourceIDMetaMap)
if len(newFilters) == 0 {
return result
}
for _, pattern := range newFilters {
for k := range result {
match, _ := regexp.MatchString(pattern, result[k].Name)
if match {
newResourceMap[k] = result[k]
}
}
}
return newResourceMap
}
func ExcludeFilterResourceByRegex(result resourceExporter.ResourceIDMetaMap, name string, filter []string) resourceExporter.ResourceIDMetaMap {
newFilters := make([]string, 0)
for _, f := range filter {
if strings.Contains(f, "::") && strings.Split(f, "::")[0] == name {
i := strings.Index(f, "::")
regexStr := f[i+2:]
newFilters = append(newFilters, regexStr)
}
}
if len(newFilters) == 0 {
return result
}
newResourceMap := make(resourceExporter.ResourceIDMetaMap)
for k := range result {
for _, pattern := range newFilters {
match, _ := regexp.MatchString(pattern, result[k].Name)
if !match {
newResourceMap[k] = result[k]
} else {
delete(newResourceMap, k)
break
}
}
}
return newResourceMap
}
/*
This file is used to hold common methods that are used across the exporter. They do not have strong affinity to any one particular export process (e.g. HCL or JSON).
*/
func determineVarValue(s *schema.Schema) interface{} {
if s.Default != nil {
if m, ok := s.Default.(map[string]string); ok {
stringMap := make(map[string]interface{})
for k, v := range m {
stringMap[k] = v
}
return stringMap
}
return s.Default
}
switch s.Type {
case schema.TypeString:
return ""
case schema.TypeInt:
return 0
case schema.TypeFloat:
return 0.0
case schema.TypeBool:
return false
default:
if properties, ok := s.Elem.(*schema.Resource); ok {
propertyMap := make(map[string]interface{})
for k, v := range properties.Schema {
propertyMap[k] = determineVarValue(v)
}
return propertyMap
}
}
return nil
}
// Correct exported e164 number e.g. +(1) 111-222-333 --> +1111222333
func sanitizeE164Number(number string) string {
charactersToRemove := []string{" ", "-", "(", ")"}
for _, c := range charactersToRemove {
number = strings.Replace(number, c, "", -1)
}
return number
}
func sanitizeRrule(input string) string {
attributeRegex := map[string]*regexp.Regexp{
"INTERVAL": regexp.MustCompile(`INTERVAL=([1-9][0-9]*|0?[1-9][0-9]*);`),
"BYMONTH": regexp.MustCompile(`BYMONTH=(0?[1-9]|1[0-2]);`),
"BYMONTHDAY": regexp.MustCompile(`BYMONTHDAY=(0?[1-9]|[1-2][0-9]|3[0-1])$`),
}
// Iterate over attributes and modify the input string
for attributeName, regex := range attributeRegex {
input = regex.ReplaceAllStringFunc(input, func(match string) string {
return removeTrailingZeros(match, attributeName)
})
}
return input
}
func removeTrailingZeros(match, attributeName string) string {
pattern := `=(\d{1,2})`
re := regexp.MustCompile(pattern)
outputText := re.ReplaceAllStringFunc(match, func(match string) string {
numericPart := match[1:]
numericPart = fmt.Sprintf("%d", parseInt(numericPart))
return "=" + numericPart
})
return outputText
}
func parseInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}
// Get a string path to the target export file
func getFilePath(d *schema.ResourceData, filename string) (string, diag.Diagnostics) {
directory, diagErr := getDirPath(d)
if diagErr != nil {
return "", diagErr
}
path := filepath.Join(directory, filename)
if path == "" {
return "", diag.Errorf("Failed to create file path with directory %s", directory)
}
return path, nil
}
// Get a string path to the target export directory
func getDirPath(d *schema.ResourceData) (string, diag.Diagnostics) {
directory := d.Get("directory").(string)
if strings.HasPrefix(directory, "~") {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", diag.Errorf("Failed to evaluate home directory: %v", err)
}
directory = strings.Replace(directory, "~", homeDir, 1)
}
if err := os.MkdirAll(directory, os.ModePerm); err != nil {
return "", diag.FromErr(err)
}
return directory, nil
}
// Checks if a directory path is empty
func isDirEmpty(path string) (bool, diag.Diagnostics) {
f, err := os.Open(path)
if err != nil {
return false, diag.FromErr(err)
}
defer f.Close()
_, err = f.Readdirnames(1)
if err == io.EOF {
return true, nil
}
return false, diag.FromErr(err)
}
func createUnresolvedAttrKey(attr unresolvableAttributeInfo) string {
return fmt.Sprintf("%s_%s_%s", attr.ResourceType, attr.ResourceName, attr.Name)
}
package tfexporter
import (
"context"
"fmt"
"hash/fnv"
"log"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
dependentconsumers "terraform-provider-genesyscloud/genesyscloud/dependent_consumers"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
rRegistrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"terraform-provider-genesyscloud/genesyscloud/util"
featureToggles "terraform-provider-genesyscloud/genesyscloud/util/feature_toggles"
"terraform-provider-genesyscloud/genesyscloud/util/files"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/stringmap"
"time"
"github.com/google/uuid"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/mohae/deepcopy"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
/*
This file contains all of the logic associated wite the process of exporting a file.
*/
// Used to store the TF config block as a string so that it can be ignored when testing the exported HCL config file.
var (
terraformHCLBlock string
mockError diag.Diagnostics
// UID : "jsonencode({decoded representation of json string})"
// Attrs which can be exported in jsonencode objects are populated with a UID
// The same UID is stored as the key in attributesDecoded, with the value being the jsonencode representation of the json string.
// When the bytes are being written to the file, the UID is found and replaced with the unquoted jsonencode object
attributesDecoded = make(map[string]string)
providerDataSources map[string]*schema.Resource
providerResources map[string]*schema.Resource
resourceExporters map[string]*resourceExporter.ResourceExporter
)
type unresolvableAttributeInfo struct {
ResourceType string
ResourceName string
Name string
Schema *schema.Schema
}
type GenesysCloudResourceExporter struct {
configExporter Exporter
filterType ExporterFilterType
resourceTypeFilter ExporterResourceTypeFilter
resourceFilter ExporterResourceFilter
filterList *[]string
exportAsHCL bool
splitFilesByResource bool
logPermissionErrors bool
addDependsOn bool
replaceWithDatasource []string
includeStateFile bool
version string
provider *schema.Provider
exportDirPath string
exporters *map[string]*resourceExporter.ResourceExporter
resources []resourceExporter.ResourceInfo
resourceTypesHCLBlocks map[string]resourceHCLBlock
resourceTypesMaps map[string]resourceJSONMaps
dataSourceTypesMaps map[string]resourceJSONMaps
unresolvedAttrs []unresolvableAttributeInfo
d *schema.ResourceData
ctx context.Context
meta interface{}
dependsList map[string][]string
buildSecondDeps map[string][]string
exMutex sync.RWMutex
cyclicDependsList []string
ignoreCyclicDeps bool
flowResourcesList []string
}
func configureExporterType(ctx context.Context, d *schema.ResourceData, gre *GenesysCloudResourceExporter, filterType ExporterFilterType) {
switch filterType {
case LegacyInclude:
var filter []string
if resourceTypes, ok := d.GetOk("resource_types"); ok {
filter = lists.InterfaceListToStrings(resourceTypes.([]interface{}))
gre.filterList = &filter
}
//Setting up the resource type filter
gre.resourceTypeFilter = IncludeFilterByResourceType //Setting up the resource type filter
gre.resourceFilter = FilterResourceByName //Setting up the resource filters
case IncludeResources:
var filter []string
if resourceTypes, ok := d.GetOk("include_filter_resources"); ok {
filter = lists.InterfaceListToStrings(resourceTypes.([]interface{}))
gre.filterList = &filter
}
//Setting up the resource type filter
gre.resourceTypeFilter = IncludeFilterByResourceType //Setting up the resource type filter
gre.resourceFilter = IncludeFilterResourceByRegex //Setting up the resource filters
case ExcludeResources:
var filter []string
if resourceTypes, ok := d.GetOk("exclude_filter_resources"); ok {
filter = lists.InterfaceListToStrings(resourceTypes.([]interface{}))
gre.filterList = &filter
}
//Setting up the resource type filter
gre.resourceTypeFilter = ExcludeFilterByResourceType //Setting up the resource type filter
gre.resourceFilter = ExcludeFilterResourceByRegex //Setting up the resource filters
}
}
func NewGenesysCloudResourceExporter(ctx context.Context, d *schema.ResourceData, meta interface{}, filterType ExporterFilterType) (*GenesysCloudResourceExporter, diag.Diagnostics) {
if providerResources == nil {
providerResources, providerDataSources = rRegistrar.GetResources()
}
gre := &GenesysCloudResourceExporter{
exportAsHCL: d.Get("export_as_hcl").(bool),
splitFilesByResource: d.Get("split_files_by_resource").(bool),
logPermissionErrors: d.Get("log_permission_errors").(bool),
addDependsOn: d.Get("enable_dependency_resolution").(bool),
filterType: filterType,
includeStateFile: d.Get("include_state_file").(bool),
ignoreCyclicDeps: d.Get("ignore_cyclic_deps").(bool),
version: meta.(*provider.ProviderMeta).Version,
provider: provider.New(meta.(*provider.ProviderMeta).Version, providerResources, providerDataSources)(),
d: d,
ctx: ctx,
meta: meta,
}
err := gre.setUpExportDirPath()
if err != nil {
return nil, err
}
gre.setupDataSource()
//Setting up the filter
configureExporterType(ctx, d, gre, filterType)
return gre, nil
}
func (g *GenesysCloudResourceExporter) Export() (diagErr diag.Diagnostics) {
// Step #1 Retrieve the exporters we are have registered and have been requested by the user
diagErr = g.retrieveExporters()
if diagErr != nil {
return diagErr
}
// Step #2 Retrieve all of the individual resources we are going to export
diagErr = g.retrieveSanitizedResourceMaps()
if diagErr != nil {
return diagErr
}
// Step #3 Retrieve the individual genesys cloud object instances
diagErr = g.retrieveGenesysCloudObjectInstances()
if diagErr != nil {
return diagErr
}
// Step #4 export dependent resources for the flows
diagErr = g.buildAndExportDependsOnResourcesForFlows()
if diagErr != nil {
return diagErr
}
// Step #5 Convert the Genesys Cloud resources to neutral format (e.g. map of maps)
diagErr = g.buildResourceConfigMap()
if diagErr != nil {
return diagErr
}
// Step #6 export dependents for other resources
diagErr = g.buildAndExportDependentResources()
if diagErr != nil {
return diagErr
}
// Step #7 Write the terraform state file along with either the HCL or JSON
diagErr = g.generateOutputFiles()
if diagErr != nil {
return diagErr
}
// step #8 Verify the terraform state file with Exporter Resources
g.verifyTerraformState()
return nil
}
func (g *GenesysCloudResourceExporter) setUpExportDirPath() (diagErr diag.Diagnostics) {
log.Printf("Setting up export directory path")
g.exportDirPath, diagErr = getDirPath(g.d)
if diagErr != nil {
return diagErr
}
return nil
}
func (g *GenesysCloudResourceExporter) setupDataSource() {
if replaceWithDatasource, ok := g.d.GetOk("replace_with_datasource"); ok {
dataSourceList := lists.InterfaceListToStrings(replaceWithDatasource.([]interface{}))
g.replaceWithDatasource = dataSourceList
}
SetDataSourceExports()
g.replaceWithDatasource = append(g.replaceWithDatasource, DataSourceExports...)
}
// retrieveExporters will return a list of all the registered exporters. If the resource_type on the exporter contains any elements, only the defined
// elements in the resource_type attribute will be returned.
func (g *GenesysCloudResourceExporter) retrieveExporters() (diagErr diag.Diagnostics) {
log.Printf("Retrieving exporters list")
exports := resourceExporter.GetResourceExporters()
log.Printf("Retrieving exporters list %v", g.filterList)
if g.resourceTypeFilter != nil && g.filterList != nil {
exports = g.resourceTypeFilter(exports, *g.filterList)
}
g.exporters = &exports
// Assign excluded attributes to the config Map
if excludedAttrs, ok := g.d.GetOk("exclude_attributes"); ok {
if diagErr := g.populateConfigExcluded(*g.exporters, lists.InterfaceListToStrings(excludedAttrs.([]interface{}))); diagErr != nil {
return diagErr
}
}
return nil
}
// Removes the ::resource_name from the resource_types list
func formatFilter(filter []string) []string {
newFilter := make([]string, 0)
for _, str := range filter {
newFilter = append(newFilter, strings.Split(str, "::")[0])
}
return newFilter
}
// retrieveSanitizedResourceMaps will retrieve a list of all of the resources to be exported. It will also apply a filter (e.g the :: ) and only return the specific Genesys Cloud
// resources that are specified via :: delimiter
func (g *GenesysCloudResourceExporter) retrieveSanitizedResourceMaps() (diagErr diag.Diagnostics) {
log.Printf("Retrieving map of Genesys Cloud resources to export")
var filter []string
if exportableResourceTypes, ok := g.d.GetOk("resource_types"); ok {
filter = lists.InterfaceListToStrings(exportableResourceTypes.([]interface{}))
}
if exportableResourceTypes, ok := g.d.GetOk("include_filter_resources"); ok {
filter = lists.InterfaceListToStrings(exportableResourceTypes.([]interface{}))
}
if exportableResourceTypes, ok := g.d.GetOk("exclude_filter_resources"); ok {
filter = lists.InterfaceListToStrings(exportableResourceTypes.([]interface{}))
}
newFilter := make([]string, 0)
for _, f := range filter {
if strings.Contains(f, "::") {
newFilter = append(newFilter, f)
}
}
//Retrieve a map of all of the objects we are going to build. Apply the filter that will remove specific classes of an object
diagErr = g.buildSanitizedResourceMaps(*g.exporters, newFilter, g.logPermissionErrors)
if diagErr != nil {
return diagErr
}
//Check to see if we found any exporters. If we did find the exporter
if len(*g.exporters) == 0 {
return diag.Errorf("No valid resource types to export.")
}
return nil
}
// retrieveGenesysCloudObjectInstances will take a list of exporters and then return the actual terraform Genesys Cloud data
func (g *GenesysCloudResourceExporter) retrieveGenesysCloudObjectInstances() diag.Diagnostics {
log.Printf("Retrieving Genesys Cloud objects from Genesys Cloud")
// Retrieves data on each individual Genesys Cloud object from each registered exporter
errorChan := make(chan diag.Diagnostics)
wgDone := make(chan bool)
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(g.ctx)
defer cancel()
// We use concurrency here to spin off each exporter type and getting the data
for resType, exporter := range *g.exporters {
wg.Add(1)
go func(resType string, exporter *resourceExporter.ResourceExporter) {
defer wg.Done()
typeResources, err := g.getResourcesForType(resType, g.provider, exporter, g.meta)
if err != nil {
select {
case <-ctx.Done():
case errorChan <- err:
}
cancel()
return
}
g.resources = append(g.resources, typeResources...)
}(resType, exporter)
}
go func() {
wg.Wait()
close(wgDone)
}()
// Wait until either WaitGroup is done or an error is received
select {
case <-wgDone:
case err := <-errorChan:
return err
}
return nil
}
// buildResourceConfigMap Builds a map of all the Terraform resources data returned for each resource
func (g *GenesysCloudResourceExporter) buildResourceConfigMap() diag.Diagnostics {
log.Printf("Build Genesys Cloud Resources Map")
g.resourceTypesMaps = make(map[string]resourceJSONMaps)
g.dataSourceTypesMaps = make(map[string]resourceJSONMaps)
g.resourceTypesHCLBlocks = make(map[string]resourceHCLBlock, 0)
g.unresolvedAttrs = make([]unresolvableAttributeInfo, 0)
for _, resource := range g.resources {
jsonResult, diagErr := g.instanceStateToMap(resource.State, resource.CtyType)
isDataSource := g.isDataSource(resource.Type, resource.Name)
if diagErr != nil {
return diagErr
}
if g.resourceTypesMaps[resource.Type] == nil {
g.resourceTypesMaps[resource.Type] = make(resourceJSONMaps)
}
if len(g.resourceTypesMaps[resource.Type][resource.Name]) > 0 || len(g.dataSourceTypesMaps[resource.Type][resource.Name]) > 0 {
algorithm := fnv.New32()
algorithm.Write([]byte(uuid.NewString()))
resource.Name = resource.Name + "_" + strconv.FormatUint(uint64(algorithm.Sum32()), 10)
g.updateSanitiseMap(*g.exporters, resource)
}
if !isDataSource {
// Removes zero values and sets proper reference expressions
unresolved, _ := g.sanitizeConfigMap(resource.Type, resource.Name, jsonResult, "", *g.exporters, g.includeStateFile, g.exportAsHCL, true)
if len(unresolved) > 0 {
g.unresolvedAttrs = append(g.unresolvedAttrs, unresolved...)
}
} else {
g.sanitizeDataConfigMap(jsonResult)
}
// TODO put this in separate call
exporters := *g.exporters
if resourceFilesWriterFunc := exporters[resource.Type].CustomFileWriter.RetrieveAndWriteFilesFunc; resourceFilesWriterFunc != nil {
exportDir, _ := getFilePath(g.d, "")
if err := resourceFilesWriterFunc(resource.State.ID, exportDir, exporters[resource.Type].CustomFileWriter.SubDirectory, jsonResult, g.meta); err != nil {
log.Printf("An error has occurred while trying invoking the RetrieveAndWriteFilesFunc for resource type %s: %v", resource.Type, err)
}
}
if g.exportAsHCL {
if _, ok := g.resourceTypesHCLBlocks[resource.Type]; !ok {
g.resourceTypesHCLBlocks[resource.Type] = make(resourceHCLBlock, 0)
}
g.resourceTypesHCLBlocks[resource.Type] = append(g.resourceTypesHCLBlocks[resource.Type], instanceStateToHCLBlock(resource.Type, resource.Name, jsonResult, isDataSource))
}
if isDataSource {
if g.dataSourceTypesMaps[resource.Type] == nil {
g.dataSourceTypesMaps[resource.Type] = make(resourceJSONMaps)
}
g.dataSourceTypesMaps[resource.Type][resource.Name] = jsonResult
} else {
g.resourceTypesMaps[resource.Type][resource.Name] = jsonResult
}
}
return nil
}
func (g *GenesysCloudResourceExporter) updateSanitiseMap(exporters map[string]*resourceExporter.ResourceExporter, //Map of all of the exporters
resource resourceExporter.ResourceInfo) {
if exporters[resource.Type] != nil {
// Get the sanitized name from the ID returned as a reference expression
if idMetaMap := exporters[resource.Type].SanitizedResourceMap; idMetaMap != nil {
if meta := idMetaMap[resource.State.ID]; meta != nil && meta.Name != "" {
meta.Name = resource.Name
}
}
}
}
func (g *GenesysCloudResourceExporter) instanceStateToMap(state *terraform.InstanceState, ctyType cty.Type) (util.JsonMap, diag.Diagnostics) {
stateVal, err := schema.StateValueFromInstanceState(state, ctyType)
if err != nil {
return nil, diag.FromErr(err)
}
jsonMap, err := schema.StateValueToJSONMap(stateVal, ctyType)
if err != nil {
return nil, diag.FromErr(err)
}
return jsonMap, nil
}
// generateOutputFiles is used to generate the tfStateFile and either the tf export or the json based export
func (g *GenesysCloudResourceExporter) generateOutputFiles() diag.Diagnostics {
providerSource := g.sourceForVersion(g.version)
if g.includeStateFile {
t := NewTFStateWriter(g.ctx, g.resources, g.d, providerSource)
if err := t.writeTfState(); err != nil {
return err
}
}
var err diag.Diagnostics
if g.exportAsHCL {
hclExporter := NewHClExporter(g.resourceTypesHCLBlocks, g.unresolvedAttrs, providerSource, g.version, g.exportDirPath, g.splitFilesByResource)
err = hclExporter.exportHCLConfig()
} else {
jsonExporter := NewJsonExporter(g.resourceTypesMaps, g.dataSourceTypesMaps, g.unresolvedAttrs, providerSource, g.version, g.exportDirPath, g.splitFilesByResource)
err = jsonExporter.exportJSONConfig()
}
if err != nil {
return err
}
if g.cyclicDependsList != nil && len(g.cyclicDependsList) > 0 {
err = files.WriteToFile([]byte(strings.Join(g.cyclicDependsList, "\n")), filepath.Join(g.exportDirPath, "cyclicDepends.txt"))
if err != nil {
return err
}
}
return nil
}
func (g *GenesysCloudResourceExporter) buildAndExportDependsOnResourcesForFlows() diag.Diagnostics {
if g.addDependsOn {
filterList, resources, err := g.processAndBuildDependencies()
if err != nil {
return err
}
if len(filterList) > 0 {
diagErr := g.exportDependentResources(filterList, resources)
if diagErr != nil {
return diagErr
}
}
return nil
}
return nil
}
func (g *GenesysCloudResourceExporter) processAndBuildDependencies() (filters []string, resources resourceExporter.ResourceIDMetaMap, diagErr diag.Diagnostics) {
filterList := make([]string, 0)
totalResources := make(resourceExporter.ResourceIDMetaMap)
proxy := dependentconsumers.GetDependentConsumerProxy(nil)
retrieveDependentConsumers := func(resourceKeys resourceExporter.ResourceInfo) func(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, *resourceExporter.DependencyResource, diag.Diagnostics) {
return func(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, *resourceExporter.DependencyResource, diag.Diagnostics) {
proxy = dependentconsumers.GetDependentConsumerProxy(clientConfig)
resources := make(resourceExporter.ResourceIDMetaMap)
resources, dependsMap, err := proxy.GetDependentConsumers(ctx, resourceKeys)
if err != nil {
return nil, nil, diag.Errorf("Failed to retrieve Dependent Flows %s: %s", resourceKeys.State.ID, err)
}
return resources, dependsMap, nil
}
}
for _, resourceKeys := range g.resources {
exists := util.StringExists(resourceKeys.State.ID, g.flowResourcesList)
if exists {
log.Printf("dependent consumers retrieved %v", resourceKeys.State.ID)
continue
}
resources, dependsStruct, err := proxy.GetAllWithPooledClient(retrieveDependentConsumers(resourceKeys))
g.flowResourcesList = append(g.flowResourcesList, resourceKeys.State.ID)
if err != nil {
return nil, nil, err
}
if len(resources) > 0 {
resourcesTobeExported := retrieveExportResources(g.resources, resources)
for _, meta := range resourcesTobeExported {
resource := strings.Split(meta.Name, "::::")
filterList = append(filterList, fmt.Sprintf("%s::%s", resource[0], resource[1]))
}
g.dependsList = stringmap.MergeMaps(g.dependsList, dependsStruct.DependsMap)
g.cyclicDependsList = append(g.cyclicDependsList, dependsStruct.CyclicDependsList...)
totalResources = stringmap.MergeSingularMaps(totalResources, resources)
}
}
if !g.ignoreCyclicDeps && len(g.cyclicDependsList) > 0 {
return nil, nil, diag.Errorf("Cyclic Dependencies Identified: %v ", strings.Join(g.cyclicDependsList, "\n"))
}
return filterList, totalResources, nil
}
func (g *GenesysCloudResourceExporter) rebuildExports(filterList []string) (diagErr diag.Diagnostics) {
log.Printf("rebuild exporters list")
diagErr = g.retrieveExporters()
if diagErr != nil {
return diagErr
}
diagErr = g.buildSanitizedResourceMaps(*g.exporters, filterList, g.logPermissionErrors)
if diagErr != nil {
return diagErr
}
diagErr = g.retrieveGenesysCloudObjectInstances()
if diagErr != nil {
return diagErr
}
return nil
}
func (g *GenesysCloudResourceExporter) exportDependentResources(filterList []string, resources resourceExporter.ResourceIDMetaMap) (diagErr diag.Diagnostics) {
g.reAssignFilters()
g.filterList = &filterList
existingExporters := g.copyExporters()
existingResources := g.copyResources()
log.Printf("rebuild exports from exportDependentResources")
err := g.rebuildExports(filterList)
if err != nil {
return err
}
// retain the exporters and resources
g.retainExporterList(resources)
uniqueResources := g.attainUniqueResourceList(resources)
// deep copy is needed here else exporters being overridden
depExporters := g.copyExporters()
// this is done before the merge of exporters and this will make sure only dependency resources are resolved
g.buildResourceConfigMap()
g.exportAndResolveDependencyAttributes()
g.appendResources(uniqueResources)
g.appendResources(existingResources)
g.exporters = mergeExporters(existingExporters, *mergeExporters(depExporters, *g.exporters))
return nil
}
func (g *GenesysCloudResourceExporter) buildAndExportDependentResources() (diagErr diag.Diagnostics) {
if g.addDependsOn {
g.reAssignFilters()
existingExporters := g.copyExporters()
existingResources := g.copyResources()
// this will make sure all the dependency resources are resolved
g.exportAndResolveDependencyAttributes()
// merge the resources and exporters after the dependencies are resolved
g.appendResources(existingResources)
g.exporters = mergeExporters(existingExporters, *g.exporters)
// rebuild the config map
diagErr = g.buildResourceConfigMap()
if diagErr != nil {
return diagErr
}
}
return nil
}
func (g *GenesysCloudResourceExporter) copyExporters() map[string]*resourceExporter.ResourceExporter {
// deep copy is needed here else exporters are being overridden
existingExportersInterface := deepcopy.Copy(*g.exporters)
existingExporters, _ := existingExportersInterface.(map[string]*resourceExporter.ResourceExporter)
return existingExporters
}
func (g *GenesysCloudResourceExporter) copyResources() []resourceExporter.ResourceInfo {
existingResources := g.copyResource()
g.resources = nil
return existingResources
}
func (g *GenesysCloudResourceExporter) copyResource() []resourceExporter.ResourceInfo {
existingResources := make([]resourceExporter.ResourceInfo, 0)
for _, resource := range g.resources {
existingResource := extractResource(resource)
existingResources = append(existingResources, existingResource)
}
return existingResources
}
func (g *GenesysCloudResourceExporter) copyResourceAddtoG(resourcesToAdd []resourceExporter.ResourceInfo) {
existingResources := make([]resourceExporter.ResourceInfo, 0)
for _, resource := range resourcesToAdd {
existingResource := extractResource(resource)
existingResources = append(existingResources, existingResource)
}
g.resources = existingResources
}
func extractResource(resource resourceExporter.ResourceInfo) resourceExporter.ResourceInfo {
existingResourceInterface := deepcopy.Copy(resource)
existingResource, _ := existingResourceInterface.(resourceExporter.ResourceInfo)
existingResourceCtyTypeInterface := deepcopy.Copy(resource.CtyType)
existingResource.CtyType, _ = existingResourceCtyTypeInterface.(cty.Type)
if existingResource.CtyType == cty.NilType {
existingResource.CtyType = resource.CtyType
}
return existingResource
}
func (g *GenesysCloudResourceExporter) retainExporterList(resources resourceExporter.ResourceIDMetaMap) diag.Diagnostics {
removeChan := make([]string, 0)
for _, exporter := range *g.exporters {
for id, _ := range exporter.SanitizedResourceMap {
_, exists := resources[id]
if !exists {
removeChan = append(removeChan, id)
}
}
for _, removeId := range removeChan {
log.Printf("Deleted removeId %v", removeId)
delete(exporter.SanitizedResourceMap, removeId)
}
}
return nil
}
func (g *GenesysCloudResourceExporter) reAssignFilters() {
g.resourceTypeFilter = IncludeFilterByResourceType
g.resourceFilter = FilterResourceById
}
func (g *GenesysCloudResourceExporter) attainUniqueResourceList(resources resourceExporter.ResourceIDMetaMap) []resourceExporter.ResourceInfo {
uniqueResources := make([]resourceExporter.ResourceInfo, 0)
for _, resource := range g.resources {
_, exists := resources[resource.State.ID]
if exists {
uniqueResources = append(uniqueResources, resource)
}
}
return uniqueResources
}
func (g *GenesysCloudResourceExporter) exportAndResolveDependencyAttributes() (diagErr diag.Diagnostics) {
if g.addDependsOn {
g.resources = nil
exp := make(map[string]*resourceExporter.ResourceExporter, 0)
filterListById := make([]string, 0)
// build filter list with guid.
for refType, guidList := range g.buildSecondDeps {
if refType != "" {
for _, guid := range guidList {
if guid != "" {
filterListById = append(filterListById, fmt.Sprintf("%s::%s", refType, guid))
}
}
}
}
if len(filterListById) > 0 {
g.resourceFilter = FilterResourceById
g.chainDependencies(make([]resourceExporter.ResourceInfo, 0), exp)
}
}
return nil
}
// Recursive function to perform operations based on filterListById length
func (g *GenesysCloudResourceExporter) chainDependencies(
existingResources []resourceExporter.ResourceInfo,
existingExporters map[string]*resourceExporter.ResourceExporter) (diagErr diag.Diagnostics) {
filterListById := make([]string, 0)
for refType, guidList := range g.buildSecondDeps {
if refType != "" {
for _, guid := range guidList {
if guid != "" {
if !g.resourceIdExists(guid, existingResources) {
filterListById = append(filterListById, fmt.Sprintf("%s::%s", refType, guid))
} else {
log.Printf("Resource already present in the resources. %v", guid)
}
}
}
}
}
g.filterList = &filterListById
g.buildSecondDeps = nil
if len(*g.filterList) > 0 {
g.resources = nil
g.exporters = nil
log.Printf("rebuild exporters list from chainDependencies")
err := g.rebuildExports(*g.filterList)
if err != nil {
return err
}
// checks and exports if there are any dependent flow resources
err = g.buildAndExportDependsOnResourcesForFlows()
if err != nil {
return err
}
err = g.buildResourceConfigMap()
if err != nil {
return err
}
//append the resources and exporters
g.appendResources(existingResources)
g.exporters = mergeExporters(existingExporters, *g.exporters)
// deep copy is needed here else exporters being overridden
existingExportersInterface := deepcopy.Copy(*g.exporters)
existingExporters, _ = existingExportersInterface.(map[string]*resourceExporter.ResourceExporter)
existingResources = g.resources
// Recursive call until all the dependencies are addressed.
return g.chainDependencies(existingResources, existingExporters)
}
return nil
}
func (g *GenesysCloudResourceExporter) sourceForVersion(version string) string {
providerSource := "registry.terraform.io/mypurecloud/genesyscloud"
if g.version == "0.1.0" {
// Force using local dev version by providing a unique repo URL
providerSource = "genesys.com/mypurecloud/genesyscloud"
}
return providerSource
}
func (g *GenesysCloudResourceExporter) appendResources(resourcesToAdd []resourceExporter.ResourceInfo) {
existingResources := g.copyResource()
for _, resourceToAdd := range resourcesToAdd {
// Check if the resource with the same ID already exists
duplicate := false
for _, existingResource := range g.resources {
if existingResource.State.ID == resourceToAdd.State.ID && existingResource.Type == resourceToAdd.Type {
duplicate = true
break
}
}
// No duplicate found, append the resource
if !duplicate {
existingResources = append(existingResources, resourceToAdd)
}
}
g.copyResourceAddtoG(existingResources)
}
func (g *GenesysCloudResourceExporter) buildSanitizedResourceMaps(exporters map[string]*resourceExporter.ResourceExporter, filter []string, logErrors bool) diag.Diagnostics {
errorChan := make(chan diag.Diagnostics)
wgDone := make(chan bool)
// Cancel remaining goroutines if an error occurs
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg sync.WaitGroup
for name, exporter := range exporters {
wg.Add(1)
go func(name string, exporter *resourceExporter.ResourceExporter) {
defer wg.Done()
log.Printf("Getting all resources for type %s", name)
exporter.FilterResource = g.resourceFilter
err := exporter.LoadSanitizedResourceMap(ctx, name, filter)
// Used in tests
if mockError != nil {
err = mockError
}
if containsPermissionsErrorOnly(err) && logErrors {
log.Printf("%v", err[0].Summary)
log.Print("log_permission_errors = true. Resuming export...")
return
}
if err != nil {
if !logErrors {
err = addLogAttrInfoToErrorSummary(err)
}
select {
case <-ctx.Done():
case errorChan <- err:
}
cancel()
return
}
log.Printf("Found %d resources for type %s", len(exporter.SanitizedResourceMap), name)
}(name, exporter)
}
go func() {
wg.Wait()
close(wgDone)
}()
// Wait until either WaitGroup is done or an error is received
select {
case <-wgDone:
return nil
case err := <-errorChan:
return err
}
}
func mergeExporters(m1, m2 map[string]*resourceExporter.ResourceExporter) *map[string]*resourceExporter.ResourceExporter {
result := make(map[string]*resourceExporter.ResourceExporter)
for k, v := range m1 {
result[k] = v
}
for k, v := range m2 {
_, exists := result[k]
if exists {
for id, value := range v.SanitizedResourceMap {
result[k].SanitizedResourceMap[id] = value
}
if result[k].ExcludedAttributes != nil {
result[k].ExcludedAttributes = append(result[k].ExcludedAttributes, v.ExcludedAttributes...)
} else {
result[k].ExcludedAttributes = v.ExcludedAttributes
}
} else {
result[k] = v
}
}
return &result
}
func retrieveExportResources(existingResources []resourceExporter.ResourceInfo, resources resourceExporter.ResourceIDMetaMap) map[string]*resourceExporter.ResourceMeta {
foundTypes := make(map[string]bool)
resourcesTobeExported := make(map[string]*resourceExporter.ResourceMeta)
for _, data := range existingResources {
if _, ok := resources[data.State.ID]; ok {
foundTypes[data.State.ID] = true
}
}
for resourceType, meta := range resources {
if !foundTypes[resourceType] {
resourcesTobeExported[resourceType] = meta
}
}
return resourcesTobeExported
}
func containsPermissionsErrorOnly(err diag.Diagnostics) bool {
foundPermissionsError := false
for _, v := range err {
if strings.Contains(v.Summary, "403") ||
strings.Contains(v.Summary, "501") {
foundPermissionsError = true
} else {
return false
}
}
return foundPermissionsError
}
var logAttrInfo = "\nTo continue exporting other resources in spite of this error, set the 'log_permission_errors' attribute to 'true'"
func addLogAttrInfoToErrorSummary(err diag.Diagnostics) diag.Diagnostics {
for i, v := range err {
if strings.Contains(v.Summary, "403") ||
strings.Contains(v.Summary, "501") {
err[i].Summary += logAttrInfo
}
}
return err
}
func (g *GenesysCloudResourceExporter) getResourcesForType(resType string, provider *schema.Provider, exporter *resourceExporter.ResourceExporter, meta interface{}) ([]resourceExporter.ResourceInfo, diag.Diagnostics) {
lenResources := len(exporter.SanitizedResourceMap)
errorChan := make(chan diag.Diagnostics, lenResources)
resourceChan := make(chan resourceExporter.ResourceInfo, lenResources)
removeChan := make(chan string, lenResources)
res := provider.ResourcesMap[resType]
if res == nil {
return nil, diag.Errorf("Resource type %v not defined", resType)
}
ctyType := res.CoreConfigSchema().ImpliedType()
var wg sync.WaitGroup
wg.Add(lenResources)
for id, resMeta := range exporter.SanitizedResourceMap {
go func(id string, resMeta *resourceExporter.ResourceMeta) {
defer wg.Done()
fetchResourceState := func() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(30)*time.Minute)
defer cancel()
// This calls into the resource's ReadContext method which
// will block until it can acquire a pooled client config object.
instanceState, err := getResourceState(ctx, res, id, resMeta, meta)
if g.isDataSource(resType, resMeta.Name) {
g.exMutex.Lock()
res = provider.DataSourcesMap[resType]
g.exMutex.Unlock()
if res == nil {
return fmt.Errorf("DataSource type %v not defined", resType)
}
schemaMap := res.SchemaMap()
attributes := make(map[string]string)
for attr, _ := range schemaMap {
if value, ok := instanceState.Attributes[attr]; ok {
attributes[attr] = value
}
}
instanceState.Attributes = attributes
}
if err != nil {
errString := fmt.Sprintf("Failed to get state for %s instance %s: %v", resType, id, err)
return fmt.Errorf(errString)
}
if instanceState == nil {
log.Printf("Resource %s no longer exists. Skipping.", resMeta.Name)
removeChan <- id // Mark for removal from the map
return nil
}
resourceChan <- resourceExporter.ResourceInfo{
State: instanceState,
Name: resMeta.Name,
Type: resType,
CtyType: ctyType,
}
return nil
}
isTimeoutError := func(err error) bool {
return strings.Contains(fmt.Sprintf("%v", err), "timeout while waiting for state to become") ||
strings.Contains(fmt.Sprintf("%v", err), "context deadline exceeded")
}
var err error
for ok := true; ok; ok = isTimeoutError(err) {
err = fetchResourceState()
if err == nil {
return
}
if !isTimeoutError(err) {
errorChan <- diag.Errorf("Failed to get state for %s instance %s: %v", resType, id, err)
}
}
}(id, resMeta)
}
go func() {
wg.Wait()
close(resourceChan)
close(removeChan)
}()
var resources []resourceExporter.ResourceInfo
for r := range resourceChan {
resources = append(resources, r)
}
// Remove resources that weren't found in this pass
for id := range removeChan {
log.Printf("Deleted resource %v", id)
delete(exporter.SanitizedResourceMap, id)
}
// Return the first error if one was received
select {
case err := <-errorChan:
return nil, err
default:
return resources, nil
}
}
func getResourceState(ctx context.Context, resource *schema.Resource, resID string, resMeta *resourceExporter.ResourceMeta, meta interface{}) (*terraform.InstanceState, diag.Diagnostics) {
// If defined, pass the full ID through the import method to generate a readable state
instanceState := &terraform.InstanceState{ID: resMeta.IdPrefix + resID}
if resource.Importer != nil && resource.Importer.StateContext != nil {
resourceDataArr, err := resource.Importer.StateContext(ctx, resource.Data(instanceState), meta)
if err != nil {
return nil, diag.FromErr(err)
}
if len(resourceDataArr) > 0 {
instanceState = resourceDataArr[0].State()
}
}
state, err := resource.RefreshWithoutUpgrade(ctx, instanceState, meta)
if err != nil {
if strings.Contains(fmt.Sprintf("%v", err), "API Error: 404") ||
strings.Contains(fmt.Sprintf("%v", err), "API Error: 410") {
return nil, nil
}
return nil, err
}
if state == nil || state.ID == "" {
// Resource no longer exists
return nil, nil
}
return state, nil
}
func correctCustomFunctions(config string) string {
config = correctInterpolatedFileShaFunctions(config)
return correctDependsOn(config, true)
}
// find & replace ${filesha256(\"...\")} with ${filesha256("...")}
func correctInterpolatedFileShaFunctions(config string) string {
correctedConfig := config
re := regexp.MustCompile(`\$\{filesha256\(\\"[^\}]*\}`)
matches := re.FindAllString(config, -1)
for _, match := range matches {
correctedMatch := strings.Replace(match, `\"`, `"`, -1)
correctedConfig = strings.Replace(correctedConfig, match, correctedMatch, -1)
}
return correctedConfig
}
// terraform doesn't accept quotes references in HCL https://discuss.hashicorp.com/t/terraform-0-12-14-released/3898
// Added a corrected HCL during export and also for JSON export
func correctDependsOn(config string, isHcl bool) string {
correctedConfig := config
re := regexp.MustCompile(`"\$dep\$([^$]+)\$dep\$"`)
matches := re.FindAllString(config, -1)
for _, match := range matches {
value := re.FindStringSubmatch(match)
if len(value) == 2 {
if !isHcl {
correctedConfig = strings.Replace(correctedConfig, match, fmt.Sprintf(`"%s"`, value[1]), -1)
} else {
correctedConfig = strings.Replace(correctedConfig, match, value[1], -1)
}
}
}
return correctedConfig
}
func (g *GenesysCloudResourceExporter) sanitizeDataConfigMap(
configMap map[string]interface{}) {
for key, val := range configMap {
if key == "id" {
// Strip off IDs from the root data source
delete(configMap, key)
}
if val == nil {
delete(configMap, key)
}
}
}
// Removes empty and zero-valued attributes from the JSON config.
// Map attributes are removed by setting them to null, as the Terraform
// attribute syntax requires attributes be set to null
// that would otherwise be optional in nested block form:
// https://www.terraform.io/docs/language/attr-as-blocks.html#arbitrary-expressions-with-argument-syntax
func (g *GenesysCloudResourceExporter) sanitizeConfigMap(
resourceType string,
resourceName string,
configMap map[string]interface{},
prevAttr string,
exporters map[string]*resourceExporter.ResourceExporter, //Map of all of the exporters
exportingState bool,
exportingAsHCL bool,
parentKey bool) ([]unresolvableAttributeInfo, bool) {
exporter := exporters[resourceType] //Get the specific export that we will be working with
unresolvableAttrs := make([]unresolvableAttributeInfo, 0)
for key, val := range configMap {
currAttr := key
wildcardAttr := "*"
if prevAttr != "" {
currAttr = prevAttr + "." + key
wildcardAttr = prevAttr + "." + "*"
}
// Identify configMap for the parent resource and add depends_on for the parent resource
if parentKey {
if currAttr == "id" {
g.addDependsOnValues(val.(string), configMap)
}
}
if currAttr == "id" {
// Strip off IDs from the root resource
delete(configMap, currAttr)
continue
}
if exporter.IsAttributeExcluded(currAttr) {
// Excluded. Remove from the config.
configMap[key] = nil
continue
}
if exporter.IsAttributeE164(currAttr) {
if phoneNumber, ok := configMap[key].(string); !ok || phoneNumber == "" {
continue
}
configMap[key] = sanitizeE164Number(configMap[key].(string))
continue
}
if exporter.IsAttributeRrule(currAttr) {
if rrule, ok := configMap[key].(string); !ok || rrule == "" {
continue
}
configMap[key] = sanitizeRrule(configMap[key].(string))
continue
}
switch val.(type) {
case map[string]interface{}:
// Maps are sanitized in-place
currMap := val.(map[string]interface{})
_, res := g.sanitizeConfigMap(resourceType, resourceName, val.(map[string]interface{}), currAttr, exporters, exportingState, exportingAsHCL, false)
if !res || len(currMap) == 0 {
// Remove empty maps or maps indicating they should be removed
configMap[key] = nil
}
case []interface{}:
if arr := g.sanitizeConfigArray(resourceType, resourceName, val.([]interface{}), currAttr, exporters, exportingState, exportingAsHCL); len(arr) > 0 {
configMap[key] = arr
} else {
// Remove empty arrays
configMap[key] = nil
}
case string:
// Check if string contains nested Ref Attributes (can occur if the string is escaped json)
if _, ok := exporter.ContainsNestedRefAttrs(currAttr); ok {
resolvedJsonString, err := g.resolveRefAttributesInJsonString(currAttr, val.(string), exporter, exporters, exportingState)
if err != nil {
log.Println(err)
} else {
keys := strings.Split(currAttr, ".")
configMap[keys[len(keys)-1]] = resolvedJsonString
break
}
}
// Check if we are on a reference attribute and update as needed
refSettings := exporter.GetRefAttrSettings(currAttr)
if refSettings == nil {
// Check for wildcard attribute indicating all attributes in the map
refSettings = exporter.GetRefAttrSettings(wildcardAttr)
}
if refSettings != nil {
configMap[key] = g.resolveReference(refSettings, val.(string), exporters, exportingState)
} else {
configMap[key] = escapeString(val.(string))
}
// custom function to resolve the field to a data source depending on the value
g.resolveValueToDataSource(exporter, configMap, currAttr, val)
}
if attr, ok := attrInUnResolvableAttrs(key, exporter.UnResolvableAttributes); ok {
varReference := fmt.Sprintf("%s_%s_%s", resourceType, resourceName, key)
unresolvableAttrs = append(unresolvableAttrs, unresolvableAttributeInfo{
ResourceType: resourceType,
ResourceName: resourceName,
Name: key,
Schema: attr,
})
if properties, ok := attr.Elem.(*schema.Resource); ok {
propertiesMap := make(map[string]interface{})
for k := range properties.Schema {
propertiesMap[k] = fmt.Sprintf("${var.%s.%s}", varReference, k)
}
configMap[key] = propertiesMap
} else {
configMap[key] = fmt.Sprintf("${var.%s}", varReference)
}
}
// The plugin SDK does not yet have a concept of "null" for unset attributes, so they are saved in state as their "zero value".
// This can cause invalid config files due to including attributes with limits that don't allow for zero values, so we remove
// those attributes from the config by default. Attributes can opt-out of this behavior by being added to a ResourceExporter's
// AllowZeroValues list.
if !exporter.AllowForZeroValues(currAttr) {
removeZeroValues(key, configMap[key], configMap)
}
// Nil arrays will be turned into empty arrays if they're defined in AllowEmptyArrays.
// We do this after the initial sanitization of empty arrays to nil
// so this will cover both cases where the attribute on the state is: null or [].
if exporter.AllowForEmptyArrays(currAttr) {
if configMap[key] == nil {
configMap[key] = []interface{}{}
}
}
//If the exporter as has customer resolver for an attribute, invoke it.
if refAttrCustomResolver, ok := exporter.CustomAttributeResolver[currAttr]; ok {
log.Printf("Custom resolver invoked for attribute: %s", currAttr)
if resolverFunc := refAttrCustomResolver.ResolverFunc; resolverFunc != nil {
if err := resolverFunc(configMap, exporters, resourceName); err != nil {
log.Printf("An error has occurred while trying invoke a custom resolver for attribute %s: %v", currAttr, err)
}
}
}
// Check if the exporter has custom flow resolver (Only applicable for flow resource)
if refAttrCustomFlowResolver, ok := exporter.CustomFlowResolver[currAttr]; ok {
log.Printf("Custom resolver invoked for attribute: %s", currAttr)
varReference := fmt.Sprintf("%s_%s_%s", resourceType, resourceName, "filepath")
if err := refAttrCustomFlowResolver.ResolverFunc(configMap, varReference); err != nil {
log.Printf("An error has occurred while trying invoke a custom resolver for attribute %s: %v", currAttr, err)
}
}
if exportingAsHCL && exporter.IsJsonEncodable(currAttr) {
if vStr, ok := configMap[key].(string); ok {
decodedData, err := getDecodedData(vStr, currAttr)
if err != nil {
log.Printf("Error decoding JSON string: %v\n", err)
configMap[key] = vStr
} else {
uid := uuid.NewString()
attributesDecoded[uid] = decodedData
configMap[key] = uid
}
}
}
}
if exporter.RemoveFieldIfMissing(prevAttr, configMap) {
// Missing some inner attributes causes the outer object to be removed
return unresolvableAttrs, false
}
return unresolvableAttrs, true
}
// resolveValueToDataSource invokes a custom resolver method to add a data source to the export and
// update an attribute to reference the data source
func (g *GenesysCloudResourceExporter) resolveValueToDataSource(exporter *resourceExporter.ResourceExporter, configMap map[string]any, attribute string, originalValue any) {
// return if ResolveToDataSourceFunc does not exist for this attribute
refAttrCustomResolver, ok := exporter.CustomAttributeResolver[attribute]
if !ok {
return
}
resolveToDataSourceFunc := refAttrCustomResolver.ResolveToDataSourceFunc
if resolveToDataSourceFunc == nil {
return
}
sdkConfig := g.meta.(*provider.ProviderMeta).ClientConfig
dataSourceType, dataSourceId, dataSourceConfig, resolve := resolveToDataSourceFunc(configMap, originalValue, sdkConfig)
if !resolve {
return
}
if g.dataSourceTypesMaps[dataSourceType] == nil {
g.dataSourceTypesMaps[dataSourceType] = make(resourceJSONMaps)
}
// add the data source to the export if it hasn't already been added
if _, ok := g.dataSourceTypesMaps[dataSourceType][dataSourceId]; ok {
return
}
g.dataSourceTypesMaps[dataSourceType][dataSourceId] = dataSourceConfig
if g.exportAsHCL {
if _, ok := g.resourceTypesHCLBlocks[dataSourceType]; !ok {
g.resourceTypesHCLBlocks[dataSourceType] = make(resourceHCLBlock, 0)
}
g.resourceTypesHCLBlocks[dataSourceType] = append(g.resourceTypesHCLBlocks[dataSourceType], instanceStateToHCLBlock(dataSourceType, dataSourceId, dataSourceConfig, true))
}
}
func attrInUnResolvableAttrs(a string, myMap map[string]*schema.Schema) (*schema.Schema, bool) {
for k, v := range myMap {
if k == a {
return v, true
}
}
return nil, false
}
func removeZeroValues(key string, val interface{}, configMap util.JsonMap) {
if val == nil || reflect.TypeOf(val).String() == "bool" {
return
}
if reflect.ValueOf(val).IsZero() {
configMap[key] = nil
}
}
// Identify the parent config map and if the resources have further dependent resources add a new attribute depends_on
func (g *GenesysCloudResourceExporter) addDependsOnValues(key string, configMap util.JsonMap) {
list, exists := g.dependsList[key]
resourceDependsList := make([]string, 0)
if exists {
for _, res := range list {
for _, resource := range g.resources {
if resource.State.ID == strings.Split(res, ".")[1] {
resourceDependsList = append(resourceDependsList, fmt.Sprintf("$dep$%s$dep$", strings.Split(res, ".")[0]+"."+resource.Name))
}
}
}
if len(resourceDependsList) > 0 {
configMap["depends_on"] = resourceDependsList
}
}
}
func escapeString(strValue string) string {
// Check for any '${' or '%{' in the exported string and escape them
// https://www.terraform.io/docs/language/expressions/strings.html#escape-sequences
escapedVal := strings.ReplaceAll(strValue, "${", "$${")
escapedVal = strings.ReplaceAll(escapedVal, "%{", "%%{")
return escapedVal
}
func (g *GenesysCloudResourceExporter) sanitizeConfigArray(
resourceType string,
resourceName string,
anArray []interface{},
currAttr string,
exporters map[string]*resourceExporter.ResourceExporter,
exportingState bool,
exportingAsHCL bool) []interface{} {
exporter := exporters[resourceType]
result := []interface{}{}
for _, val := range anArray {
switch val.(type) {
case map[string]interface{}:
// Only include in the result if sanitizeConfigMap returns true and the map is not empty
currMap := val.(map[string]interface{})
_, res := g.sanitizeConfigMap(resourceType, resourceName, currMap, currAttr, exporters, exportingState, exportingAsHCL, false)
if res && len(currMap) > 0 {
result = append(result, val)
}
case []interface{}:
if arr := g.sanitizeConfigArray(resourceType, resourceName, val.([]interface{}), currAttr, exporters, exportingState, exportingAsHCL); len(arr) > 0 {
result = append(result, arr)
}
case string:
// Check if we are on a reference attribute and update value in array
if refSettings := exporter.GetRefAttrSettings(currAttr); refSettings != nil {
referenceVal := g.resolveReference(refSettings, val.(string), exporters, exportingState)
if referenceVal != "" {
result = append(result, referenceVal)
}
} else {
result = append(result, escapeString(val.(string)))
}
default:
result = append(result, val)
}
}
return result
}
func (g *GenesysCloudResourceExporter) populateConfigExcluded(exporters map[string]*resourceExporter.ResourceExporter, configExcluded []string) diag.Diagnostics {
for _, excluded := range configExcluded {
matchFound := false
resourceIdx := strings.Index(excluded, ".")
if resourceIdx == -1 {
return diag.Errorf("Invalid excluded_attribute %s", excluded)
}
if len(excluded) == resourceIdx {
return diag.Errorf("excluded_attributes value %s does not contain an attribute", excluded)
}
resourceName := excluded[:resourceIdx]
// identify all the resource names which match the regex
exporter := exporters[resourceName]
if exporter == nil {
for name, exporter1 := range exporters {
match, _ := regexp.MatchString(resourceName, name)
if match {
excludedAttr := excluded[resourceIdx+1:]
exporter1.AddExcludedAttribute(excludedAttr)
log.Printf("Excluding attribute %s on %s resources.", excludedAttr, resourceName)
matchFound = true
continue
}
}
if !matchFound {
if dependsOn, ok := g.d.GetOk("enable_dependency_resolution"); ok {
if dependsOn == true {
excludedAttr := excluded[resourceIdx+1:]
log.Printf("Ignoring exclude attribute %s on %s resources. Since exporter is not retrieved", excludedAttr, resourceName)
continue
}
} else {
return diag.Errorf("Resource %s in excluded_attributes is not being exported.", resourceName)
}
}
} else {
excludedAttr := excluded[resourceIdx+1:]
exporter.AddExcludedAttribute(excludedAttr)
log.Printf("Excluding attribute %s on %s resources.", excludedAttr, resourceName)
}
}
return nil
}
func (g *GenesysCloudResourceExporter) resolveReference(refSettings *resourceExporter.RefAttrSettings, refID string, exporters map[string]*resourceExporter.ResourceExporter, exportingState bool) string {
if lists.ItemInSlice(refID, refSettings.AltValues) {
// This is not actually a reference to another object. Keep the value
return refID
}
if exporters[refSettings.RefType] != nil {
// Get the sanitized name from the ID returned as a reference expression
if idMetaMap := exporters[refSettings.RefType].SanitizedResourceMap; idMetaMap != nil {
if meta := idMetaMap[refID]; meta != nil && meta.Name != "" {
if g.isDataSource(refSettings.RefType, meta.Name) && g.resourceIdExists(refID, nil) {
return fmt.Sprintf("${%s.%s.%s.id}", "data", refSettings.RefType, meta.Name)
}
if g.resourceIdExists(refID, nil) {
return fmt.Sprintf("${%s.%s.id}", refSettings.RefType, meta.Name)
}
}
}
}
if g.buildSecondDeps == nil || len(g.buildSecondDeps) == 0 {
g.buildSecondDeps = make(map[string][]string)
}
if g.buildSecondDeps[refSettings.RefType] != nil {
guidList := g.buildSecondDeps[refSettings.RefType]
present := false
for _, element := range guidList {
if element == refID {
present = true // String found in the slice
}
}
if !present {
guidList = append(guidList, refID)
g.buildSecondDeps[refSettings.RefType] = guidList
}
} else {
g.buildSecondDeps[refSettings.RefType] = []string{refID}
}
if exportingState {
// Don't remove unmatched IDs when exporting state. This will keep existing config in an org
return refID
}
// No match found. Remove the value from the config since we do not have a reference to use
return ""
}
func (g *GenesysCloudResourceExporter) resourceIdExists(refID string, existingResources []resourceExporter.ResourceInfo) bool {
if g.addDependsOn {
if existingResources != nil {
for _, resource := range existingResources {
if refID == resource.State.ID {
return true
}
}
}
for _, resource := range g.resources {
if refID == resource.State.ID {
return true
}
}
log.Printf("Resource present in sanitizedConfigMap and not present in resources section %v", refID)
return false
}
return true
}
func (g *GenesysCloudResourceExporter) isDataSource(resType string, name string) bool {
for _, element := range g.replaceWithDatasource {
if element == resType+"::"+name || fetchByRegex(element, resType, name) {
return true
}
}
return false
}
func fetchByRegex(fullName string, resType string, name string) bool {
if strings.Contains(fullName, "::") && strings.Split(fullName, "::")[0] == resType {
i := strings.Index(fullName, "::")
regexStr := fullName[i+2:]
match, _ := regexp.MatchString(regexStr, name)
return match
}
return false
}
func (g *GenesysCloudResourceExporter) verifyTerraformState() diag.Diagnostics {
if exists := featureToggles.StateComparisonTrue(); exists {
if g.exportAsHCL {
tfstatePath, _ := getFilePath(g.d, defaultTfStateFile)
hclExporter := NewTfStateExportReader(tfstatePath, g.exportDirPath)
hclExporter.compareExportAndTFState()
}
}
return nil
}
package tfexporter
import (
"fmt"
"os"
"path/filepath"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
zclconfCty "github.com/zclconf/go-cty/cty"
)
/*
This file contains all of the functions used to export HCL functions.
*/
const resourceHCLFileExt = "tf"
type resourceHCLBlock [][]byte
type HCLExporter struct {
resourceTypesHCLBlocks map[string]resourceHCLBlock
unresolvedAttrs []unresolvableAttributeInfo
providerSource string
version string
dirPath string
splitFilesByResource bool
}
func NewHClExporter(resourceTypesHCLBlocks map[string]resourceHCLBlock, unresolvedAttrs []unresolvableAttributeInfo, providerSource string, version string, dirPath string, splitFilesByResource bool) *HCLExporter {
hclExporter := &HCLExporter{
resourceTypesHCLBlocks: resourceTypesHCLBlocks,
unresolvedAttrs: unresolvedAttrs,
providerSource: providerSource,
version: version,
dirPath: dirPath,
splitFilesByResource: splitFilesByResource,
}
return hclExporter
}
func (h *HCLExporter) exportHCLConfig() diag.Diagnostics {
providerBlock := createHCLProviderBlock(h.providerSource, h.version)
variablesBlock := createHCLVariablesBlock(h.unresolvedAttrs)
if h.splitFilesByResource {
// Provider file
providerHCLFilePath := filepath.Join(h.dirPath, defaultTfHCLProviderFile)
if providerHCLFilePath == "" {
return diag.Errorf("Failed to create file path %s", providerHCLFilePath)
}
if diagErr := writeHCLToFile([][]byte{providerBlock}, providerHCLFilePath); diagErr != nil {
return diagErr
}
// Variables file
variablesHCLFilePath := filepath.Join(h.dirPath, defaultTfHCLVariablesFile)
if variablesHCLFilePath == "" {
return diag.Errorf("Failed to create file path %s", variablesHCLFilePath)
}
if diagErr := writeHCLToFile([][]byte{variablesBlock}, variablesHCLFilePath); diagErr != nil {
return diagErr
}
// Resource files
for resType, resBlock := range h.resourceTypesHCLBlocks {
resourceHCLFilePath := filepath.Join(h.dirPath, fmt.Sprintf("%s.%s", resType, resourceHCLFileExt))
if resourceHCLFilePath == "" {
return diag.Errorf("Failed to create file path %s", resourceHCLFilePath)
}
if diagErr := writeHCLToFile(resBlock, resourceHCLFilePath); diagErr != nil {
return diagErr
}
}
} else {
// Single file export
allBlockSlice := make([][]byte, 0)
allBlockSlice = append(allBlockSlice, providerBlock)
for _, resBlock := range h.resourceTypesHCLBlocks {
allBlockSlice = append(allBlockSlice, resBlock...)
}
allBlockSlice = append(allBlockSlice, variablesBlock)
hclFilePath := filepath.Join(h.dirPath, defaultTfHCLFile)
if hclFilePath == "" {
return diag.Errorf("Failed to create file path %s", hclFilePath)
}
if diagErr := writeHCLToFile(allBlockSlice, hclFilePath); diagErr != nil {
return diagErr
}
}
// Optional tfvars file creation for unresolved attributes
if len(h.unresolvedAttrs) > 0 {
tfVars := make(map[string]interface{})
keys := make(map[string]string)
for _, attr := range h.unresolvedAttrs {
key := createUnresolvedAttrKey(attr)
if keys[key] != "" {
continue
}
keys[key] = key
tfVars[key] = determineVarValue(attr.Schema)
}
tfVarsFilePath := filepath.Join(h.dirPath, defaultTfVarsFile)
if tfVarsFilePath == "" {
return diag.Errorf("Failed to create tfvars file path %s", tfVarsFilePath)
}
if diagErr := writeTfVars(tfVars, tfVarsFilePath); diagErr != nil {
return diagErr
}
}
return nil
}
// Create the HCL block for terraform and the genesyscloud provider
func createHCLProviderBlock(providerSource string, version string) []byte {
rootFile := hclwrite.NewEmptyFile()
rootBody := rootFile.Body()
tfBlock := rootBody.AppendNewBlock("terraform", nil)
requiredProvidersBlock := tfBlock.Body().AppendNewBlock("required_providers", nil)
requiredProvidersBlock.Body().SetAttributeValue("genesyscloud", zclconfCty.ObjectVal(map[string]zclconfCty.Value{
"source": zclconfCty.StringVal(providerSource),
"version": zclconfCty.StringVal(version),
}))
// side effect assign to terraformHCLBlock. This is for testing.
terraformHCLBlock = string(rootFile.Bytes())
return rootFile.Bytes()
}
// Create HCL variable blocks for the unresolved attributes
func createHCLVariablesBlock(unresolvedAttrs []unresolvableAttributeInfo) []byte {
mFile := hclwrite.NewEmptyFile()
keys := make(map[string]string)
for _, attr := range unresolvedAttrs {
mBody := mFile.Body()
key := createUnresolvedAttrKey(attr)
if keys[key] != "" {
continue
}
keys[key] = key
variableBlock := mBody.AppendNewBlock("variable", []string{key})
if attr.Schema.Description != "" {
variableBlock.Body().SetAttributeValue("description", zclconfCty.StringVal(attr.Schema.Description))
}
if attr.Schema.Default != nil {
variableBlock.Body().SetAttributeValue("default", getCtyValue(attr.Schema.Default))
}
if attr.Schema.Sensitive {
variableBlock.Body().SetAttributeValue("sensitive", zclconfCty.BoolVal(attr.Schema.Sensitive))
}
}
return mFile.Bytes()
}
func postProcessHclBytes(resource []byte) []byte {
resourceStr := string(resource)
for placeholderId, val := range attributesDecoded {
resourceStr = strings.Replace(resourceStr, fmt.Sprintf("\"%s\"", placeholderId), val, -1)
}
resourceStr = correctCustomFunctions(resourceStr)
return []byte(resourceStr)
}
func writeHCLToFile(bytes [][]byte, path string) diag.Diagnostics {
// clear contents
_ = os.WriteFile(path, nil, os.ModePerm)
for _, v := range bytes {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return diag.Errorf("Error opening/creating file %s: %v", path, err)
}
v = postProcessHclBytes(v)
if _, err := f.Write(v); err != nil {
return diag.Errorf("Error writing file %s: %v", path, err)
}
_, _ = f.Write([]byte("\n"))
if err := f.Close(); err != nil {
return diag.Errorf("Error closing file %s: %v", path, err)
}
}
return nil
}
func instanceStateToHCLBlock(resType, resName string, json util.JsonMap, isDataSource bool) []byte {
f := hclwrite.NewEmptyFile()
rootBody := f.Body()
var block *hclwrite.Block
if isDataSource {
block = rootBody.AppendNewBlock("data", []string{resType, resName})
} else {
block = rootBody.AppendNewBlock("resource", []string{resType, resName})
}
body := block.Body()
addBody(body, json)
newCopy := strings.Replace(string(f.Bytes()), "$${", "${", -1)
return []byte(newCopy)
}
func addBody(body *hclwrite.Body, json util.JsonMap) {
for k, v := range json {
addValue(body, k, v)
}
}
func addValue(body *hclwrite.Body, k string, v interface{}) {
if vInter, ok := v.([]interface{}); ok {
handleInterfaceArray(body, k, vInter)
} else {
ctyVal := getCtyValue(v)
if ctyVal != zclconfCty.NilVal {
body.SetAttributeValue(k, ctyVal)
}
}
}
func getCtyValue(v interface{}) zclconfCty.Value {
var value zclconfCty.Value
if vStr, ok := v.(string); ok {
value = zclconfCty.StringVal(vStr)
} else if vBool, ok := v.(bool); ok {
value = zclconfCty.BoolVal(vBool)
} else if vInt, ok := v.(int); ok {
value = zclconfCty.NumberIntVal(int64(vInt))
} else if vInt32, ok := v.(int32); ok {
value = zclconfCty.NumberIntVal(int64(vInt32))
} else if vInt64, ok := v.(int64); ok {
value = zclconfCty.NumberIntVal(vInt64)
} else if vFloat32, ok := v.(float32); ok {
value = zclconfCty.NumberFloatVal(float64(vFloat32))
} else if vFloat64, ok := v.(float64); ok {
value = zclconfCty.NumberFloatVal(vFloat64)
} else if vMapInter, ok := v.(map[string]interface{}); ok {
value = createHCLObject(vMapInter)
} else if vMapInter, ok := v.([]string); ok {
var values []zclconfCty.Value
for _, s := range vMapInter {
values = append(values, zclconfCty.StringVal(s))
}
value = zclconfCty.ListVal(values)
} else {
value = zclconfCty.NilVal
}
return value
}
// Creates hcl objects in the format name = { item1 = "", item2 = "", ... }
func createHCLObject(v map[string]interface{}) zclconfCty.Value {
obj := make(map[string]zclconfCty.Value)
for key, val := range v {
ctyVal := getCtyValue(val)
if ctyVal != zclconfCty.NilVal {
obj[key] = ctyVal
}
}
if len(obj) == 0 {
return zclconfCty.NilVal
}
return zclconfCty.ObjectVal(obj)
}
func handleInterfaceArray(body *hclwrite.Body, k string, v []interface{}) {
var listItems []zclconfCty.Value
nestedBlock := false
for _, val := range v {
// k { ... }
if valMap, ok := val.(map[string]interface{}); ok {
block := body.AppendNewBlock(k, nil)
for key, value := range valMap {
addValue(block.Body(), key, value)
}
nestedBlock = true
// k = [ ... ]
} else {
listItems = append(listItems, getCtyValue(val))
nestedBlock = false
}
}
if len(listItems) > 0 {
body.SetAttributeValue(k, zclconfCty.ListVal(listItems))
} else if len(listItems) == 0 && !nestedBlock {
body.SetAttributeValue(k, zclconfCty.ListValEmpty(zclconfCty.NilType))
}
}
package tfexporter
import (
"encoding/json"
"fmt"
"log"
"path/filepath"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/files"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const resourceJSONFileExt = "tf.json"
type resourceJSONMaps map[string]util.JsonMap
type JsonExporter struct {
resourceTypesJSONMaps map[string]resourceJSONMaps
dataSourceTypesMaps map[string]resourceJSONMaps
unresolvedAttrs []unresolvableAttributeInfo
providerSource string
version string
dirPath string
splitFilesByResource bool
}
func NewJsonExporter(resourceTypesJSONMaps map[string]resourceJSONMaps, dataSourceTypesMaps map[string]resourceJSONMaps, unresolvedAttrs []unresolvableAttributeInfo, providerSource string, version string, dirPath string, splitFilesByResource bool) *JsonExporter {
jsonExporter := &JsonExporter{
resourceTypesJSONMaps: resourceTypesJSONMaps,
dataSourceTypesMaps: dataSourceTypesMaps,
unresolvedAttrs: unresolvedAttrs,
providerSource: providerSource,
version: version,
dirPath: dirPath,
splitFilesByResource: splitFilesByResource,
}
return jsonExporter
}
/*
This file contains all of the functions used to generate the JSON export.
*/
func (j *JsonExporter) exportJSONConfig() diag.Diagnostics {
providerJsonMap := createProviderJsonMap(j.providerSource, j.version)
variablesJsonMap := createVariablesJsonMap(j.unresolvedAttrs)
if j.splitFilesByResource {
// Provider file
terraformRoot := map[string]interface{}{
"terraform": providerJsonMap,
}
providerJSONFilePath := filepath.Join(j.dirPath, defaultTfJSONProviderFile)
if providerJSONFilePath == "" {
return diag.Errorf("Failed to create file path %s", providerJSONFilePath)
}
if diagErr := writeConfig(terraformRoot, providerJSONFilePath); diagErr != nil {
return diagErr
}
// Variables file
variablesRoot := map[string]interface{}{
"variable": variablesJsonMap,
}
variablesJSONFilePath := filepath.Join(j.dirPath, defaultTfJSONVariablesFile)
if variablesJSONFilePath == "" {
return diag.Errorf("Failed to create file path %s", variablesJSONFilePath)
}
if diagErr := writeConfig(variablesRoot, variablesJSONFilePath); diagErr != nil {
return diagErr
}
// Resource files
for resType, resJsonMap := range j.resourceTypesJSONMaps {
resourceRoot := map[string]interface{}{
"resource": util.JsonMap{
resType: resJsonMap,
},
}
resourceJSONFilePath := filepath.Join(j.dirPath, fmt.Sprintf("%s.%s", resType, resourceJSONFileExt))
if resourceJSONFilePath == "" {
return diag.Errorf("Failed to create file path %s", resourceJSONFilePath)
}
if diagErr := writeConfig(resourceRoot, resourceJSONFilePath); diagErr != nil {
return diagErr
}
}
// DataSource files
for resType, resJsonMap := range j.dataSourceTypesMaps {
resourceRoot := map[string]interface{}{
"data": util.JsonMap{
resType: resJsonMap,
},
}
resourceJSONFilePath := filepath.Join(j.dirPath, fmt.Sprintf("%s.%s", resType, resourceJSONFileExt))
if resourceJSONFilePath == "" {
return diag.Errorf("Failed to create file path %s", resourceJSONFilePath)
}
if diagErr := writeConfig(resourceRoot, resourceJSONFilePath); diagErr != nil {
return diagErr
}
}
} else {
// Single file export
rootJSONObject := util.JsonMap{
"resource": j.resourceTypesJSONMaps,
"terraform": providerJsonMap,
"data": j.dataSourceTypesMaps,
}
if len(variablesJsonMap) > 0 {
rootJSONObject["variable"] = variablesJsonMap
}
jsonFilePath := filepath.Join(j.dirPath, defaultTfJSONFile)
if jsonFilePath == "" {
return diag.Errorf("Failed to create file path %s", jsonFilePath)
}
writeConfig(rootJSONObject, jsonFilePath)
}
// Optional tfvars file creation for unresolved attributes
if len(j.unresolvedAttrs) > 0 {
tfVars := make(map[string]interface{})
for _, attr := range j.unresolvedAttrs {
key := createUnresolvedAttrKey(attr)
tfVars[key] = make(util.JsonMap)
tfVars[key] = determineVarValue(attr.Schema)
}
tfVarsFilePath := filepath.Join(j.dirPath, defaultTfVarsFile)
if tfVarsFilePath == "" {
return diag.Errorf("Failed to create tfvars file path %s", tfVarsFilePath)
}
if err := writeTfVars(tfVars, tfVarsFilePath); err != nil {
return err
}
}
return nil
}
func createProviderJsonMap(providerSource string, version string) util.JsonMap {
return util.JsonMap{
"required_providers": util.JsonMap{
"genesyscloud": util.JsonMap{
"source": providerSource,
"version": version,
},
},
}
}
func createVariablesJsonMap(unresolvedAttrs []unresolvableAttributeInfo) map[string]util.JsonMap {
variable := make(map[string]util.JsonMap)
for _, attr := range unresolvedAttrs {
key := createUnresolvedAttrKey(attr)
variable[key] = make(util.JsonMap)
variable[key]["description"] = attr.Schema.Description
if variable[key]["description"] == "" {
variable[key]["description"] = fmt.Sprintf("%s value for resource %s of type %s", attr.Name, attr.ResourceName, attr.ResourceType)
}
variable[key]["sensitive"] = attr.Schema.Sensitive
if attr.Schema.Default != nil {
variable[key]["default"] = attr.Schema.Default
}
variable[key]["type"] = determineVarType(attr.Schema)
}
return variable
}
func getDecodedData(jsonString string, currAttr string) (string, error) {
var jsonVar interface{}
err := json.Unmarshal([]byte(jsonString), &jsonVar)
if err != nil {
return "", err
}
formattedJson, err := json.MarshalIndent(jsonVar, "", "\t")
if err != nil {
return "", err
}
// replace : with = as is expected syntax in a jsonencode object
decodedJson := strings.Replace(string(formattedJson), "\": ", "\" = ", -1)
// fix indentation
numOfIndents := strings.Count(currAttr, ".") + 1
spaces := ""
for i := 0; i < numOfIndents; i++ {
spaces = spaces + " "
}
decodedJson = strings.Replace(decodedJson, "\t", fmt.Sprintf("\t%v", spaces), -1)
// add extra space before the final character (either ']' or '}')
decodedJson = fmt.Sprintf("%v%v%v", decodedJson[:len(decodedJson)-1], spaces, decodedJson[len(decodedJson)-1:])
decodedJson = fmt.Sprintf("jsonencode(%v)", decodedJson)
return decodedJson, nil
}
func (g *GenesysCloudResourceExporter) resolveRefAttributesInJsonString(currAttr string, currVal string, exporter *resourceExporter.ResourceExporter, exporters map[string]*resourceExporter.ResourceExporter, exportingState bool) (string, error) {
var jsonData interface{}
err := json.Unmarshal([]byte(currVal), &jsonData)
if err != nil {
return "", err
}
nestedAttrs, _ := exporter.ContainsNestedRefAttrs(currAttr)
for _, value := range nestedAttrs {
refSettings := exporter.GetNestedRefAttrSettings(value)
if data, ok := jsonData.(map[string]interface{}); ok {
switch data[value].(type) {
case string:
data[value] = g.resolveReference(refSettings, data[value].(string), exporters, exportingState)
case []interface{}:
array := data[value].([]interface{})
for k, v := range array {
array[k] = g.resolveReference(refSettings, v.(string), exporters, exportingState)
}
data[value] = array
}
jsonData = data
}
}
jsonDataMarshalled, err := json.Marshal(jsonData)
if err != nil {
return "", err
}
return string(jsonDataMarshalled), nil
}
func determineVarType(s *schema.Schema) string {
var varType string
switch s.Type {
case schema.TypeMap:
if elem, ok := s.Elem.(*schema.Schema); ok {
varType = fmt.Sprintf("map(%s)", determineVarType(elem))
} else {
varType = "map"
}
case schema.TypeBool:
varType = "bool"
case schema.TypeString:
varType = "string"
case schema.TypeList:
fallthrough
case schema.TypeSet:
if elem, ok := s.Elem.(*schema.Schema); ok {
varType = fmt.Sprintf("list(%s)", determineVarType(elem))
} else {
if properties, ok := s.Elem.(*schema.Resource); ok {
propPairs := ""
for k, v := range properties.Schema {
propPairs = fmt.Sprintf("%s%v = %v\n", propPairs, k, determineVarType(v))
}
varType = fmt.Sprintf("object({%s})", propPairs)
} else {
varType = "object({})"
}
}
case schema.TypeInt:
fallthrough
case schema.TypeFloat:
varType = "number"
}
return varType
}
func writeConfig(jsonMap map[string]interface{}, path string) diag.Diagnostics {
dataJSONBytes, err := json.MarshalIndent(jsonMap, "", " ")
if err != nil {
return diag.FromErr(err)
}
log.Printf("Writing export config file to %s", path)
if err := files.WriteToFile(postProcessJsonBytes(dataJSONBytes), path); err != nil {
return err
}
return nil
}
func postProcessJsonBytes(resource []byte) []byte {
resourceStr := string(resource)
resourceStr = correctDependsOn(resourceStr, false)
return []byte(resourceStr)
}
package tfexporter
import (
"context"
"fmt"
"os"
"path/filepath"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/tfexporter_state"
)
func SetRegistrar(l registrar.Registrar) {
l.RegisterResource("genesyscloud_tf_export", ResourceTfExport())
}
func ResourceTfExport() *schema.Resource {
return &schema.Resource{
Description: fmt.Sprintf(`
Genesys Cloud Resource to export Terraform config and (optionally) tfstate files to a local directory.
The config file is named '%s' or '%s', and the state file is named '%s'.
`, defaultTfJSONFile, defaultTfHCLFile, defaultTfStateFile),
CreateContext: createTfExport,
ReadContext: readTfExport,
DeleteContext: deleteTfExport,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"directory": {
Description: "Directory where the config and state files will be exported.",
Type: schema.TypeString,
Optional: true,
Default: "./genesyscloud",
ForceNew: true,
},
"resource_types": {
Description: "Resource types to export, e.g. 'genesyscloud_user'. Defaults to all exportable types. NOTE: This field is deprecated and will be removed in future release. Please use the include_filter_resources or exclude_filter_resources attribute.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: gcloud.ValidateSubStringInSlice(resourceExporter.GetAvailableExporterTypes()),
},
ForceNew: true,
Deprecated: "Use include_filter_resources attribute instead",
ConflictsWith: []string{"include_filter_resources", "exclude_filter_resources"},
},
"include_filter_resources": {
Description: "Include only resources that match either a resource type or a resource type::regular expression. See export guide for additional information",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: gcloud.ValidateSubStringInSlice(resourceExporter.GetAvailableExporterTypes()),
},
ForceNew: true,
ConflictsWith: []string{"resource_types", "exclude_filter_resources"},
},
"replace_with_datasource": {
Description: "Include only resources that match either a resource type or a resource type::regular expression. See export guide for additional information",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
ForceNew: true,
},
"exclude_filter_resources": {
Description: "Exclude resources that match either a resource type or a resource type::regular expression. See export guide for additional information",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: gcloud.ValidateSubStringInSlice(resourceExporter.GetAvailableExporterTypes()),
},
ForceNew: true,
ConflictsWith: []string{"resource_types", "include_filter_resources"},
},
"include_state_file": {
Description: "Export a 'terraform.tfstate' file along with the config file. This can be used for orgs to begin managing existing resources with terraform. When `false`, GUID fields will be omitted from the config file unless a resource reference can be supplied. In this case, the resource type will need to be included in the `resource_types` array.",
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"export_as_hcl": {
Description: "Export the config as HCL.",
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"split_files_by_resource": {
Description: "Split export files by resource type. This will also split the terraform provider and variable declarations into their own files.",
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"log_permission_errors": {
Description: "Log permission/product issues rather than fail.",
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"exclude_attributes": {
Description: "Attributes to exclude from the config when exporting resources. Each value should be of the form {resource_name}.{attribute}, e.g. 'genesyscloud_user.skills'. Excluded attributes must be optional.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
ForceNew: true,
},
"enable_dependency_resolution": {
Description: "Adds a \"depends_on\" attribute to genesyscloud_flow resources with a list of resources that are referenced inside the flow configuration . This also resolves and exports all the dependent resources for any given resource.",
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"ignore_cyclic_deps": {
Description: "Ignore Cyclic Dependencies when building the flows and do not throw an error",
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
},
}
}
func createTfExport(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
tfexporter_state.ActivateExporterState()
if _, ok := d.GetOk("include_filter_resources"); ok {
gre, _ := NewGenesysCloudResourceExporter(ctx, d, meta, IncludeResources)
diagErr := gre.Export()
if diagErr != nil {
return diagErr
}
d.SetId(gre.exportDirPath)
return nil
}
if _, ok := d.GetOk("exclude_filter_resources"); ok {
gre, _ := NewGenesysCloudResourceExporter(ctx, d, meta, ExcludeResources)
diagErr := gre.Export()
if diagErr != nil {
return diagErr
}
d.SetId(gre.exportDirPath)
return nil
}
//Dealing with the traditional resource
gre, _ := NewGenesysCloudResourceExporter(ctx, d, meta, LegacyInclude)
diagErr := gre.Export()
if diagErr != nil {
return diagErr
}
d.SetId(gre.exportDirPath)
return nil
}
// If the output directory doesn't exist or empty, mark the resource for creation.
func readTfExport(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics {
path := d.Id()
if _, err := os.Stat(path); os.IsNotExist(err) {
d.SetId("")
return nil
}
if isEmpty, diagErr := isDirEmpty(path); isEmpty || diagErr != nil {
d.SetId("")
return diagErr
}
return nil
}
// Delete everything (files and subdirectories) inside the export directory
// not including the directory itself
func deleteTfExport(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics {
exportPath := d.Id()
dir, err := os.ReadDir(exportPath)
if err != nil {
return diag.FromErr(err)
}
for _, d := range dir {
os.RemoveAll(filepath.Join(exportPath, d.Name()))
}
return nil
}
package tfexporter
import (
"encoding/json"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"io/ioutil"
"log"
"os"
"path/filepath"
"terraform-provider-genesyscloud/genesyscloud/util/files"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
)
/*
This file contains all of the functions used to compare TFstate and Exporter functions.
*/
type TfStateExportReader struct {
tfStateDirectoryPath string
exporterDirectoryPath string
}
func NewTfStateExportReader(tfStateDirectoryPath string, exporterDirectoryPath string) *TfStateExportReader {
tfStateExportReader := &TfStateExportReader{
tfStateDirectoryPath: tfStateDirectoryPath,
exporterDirectoryPath: exporterDirectoryPath,
}
return tfStateExportReader
}
func (t *TfStateExportReader) compareExportAndTFState() diag.Diagnostics {
tfStateDirectory := t.tfStateDirectoryPath
exporterDirectory := t.exporterDirectoryPath
resourceTypes := readExporterInstances(exporterDirectory)
resourcesFromTf := readTfState(tfStateDirectory)
compareStatesAndWriteFile(resourceTypes, resourcesFromTf, exporterDirectory)
return nil
}
func compareStatesAndWriteFile(resourceTypes, resourcesFromTf []string, exporterDirectoryPath string) {
diffResourceTypes := lists.SliceDifference(resourceTypes, resourcesFromTf)
jsonData := make(map[string]interface{})
if len(diffResourceTypes) > 0 {
exporterJSON := createResourceJSON("Elements present in TFState but not in Exporter", diffResourceTypes)
jsonData["MissingExporterResources"] = exporterJSON
}
diffTFState := lists.SliceDifference(resourcesFromTf, resourceTypes)
if len(diffResourceTypes) > 0 {
tfStateJSON := createResourceJSON("Elements present in Exporter but not in TFState", diffTFState)
jsonData["MissingTfStateResources"] = tfStateJSON
}
if len(jsonData) > 0 {
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
log.Printf("Error Marshalling Json %s: %v\n", jsonData, err)
return
}
log.Printf("The state and Exporter have differences:")
diagErr := files.WriteToFile(jsonBytes, filepath.Join(exporterDirectoryPath, "TFStateInconsistencies.txt"))
if diagErr != nil {
log.Printf("Error WritingFile %s: %v\n", exporterDirectoryPath, diagErr)
}
return
} else {
log.Printf("The state and Exporter are consistent.")
}
}
func createResourceJSON(description string, resources []string) map[string]interface{} {
resourceMaps := make([]map[string]string, len(resources))
for i, res := range resources {
resourceMaps[i] = map[string]string{"name": res}
}
// Create JSON structure for resources
json := map[string]interface{}{
"description": description,
"resources": resourceMaps,
}
return json
}
func processTerraformFile(path string, resourceTypes []string) []string {
// Create a new HCL parser
parser := hclparse.NewParser()
// Parse the Terraform file
content, diag := parser.ParseHCLFile(path)
if diag.HasErrors() {
log.Printf("error parsing exxport tf in %s: %v", path, diag)
return nil
}
body, _ := content.Body.(*hclsyntax.Body)
for _, block := range body.Blocks {
if len(block.Labels) > 1 {
resourceType := &block.Labels[0]
resourceName := &block.Labels[1]
resourceTypes = append(resourceTypes, *resourceType+"."+*resourceName)
}
}
return resourceTypes
}
func readExporterInstances(exporterDirectory string) []string {
var resourceTypes []string
err := filepath.Walk(exporterDirectory, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Printf("Error processing %s: %v\n", path, err)
return nil
}
// Check if the current item is a file
if !info.IsDir() && filepath.Ext(path) == ".tf" {
// Process the Terraform configuration
if resourceTypes = processTerraformFile(path, resourceTypes); err != nil {
log.Printf("Error processing %s: %v\n", path, err)
return nil
}
}
return nil
})
if err != nil {
log.Fatal(err)
return nil
}
return resourceTypes
}
func readTfState(path string) []string {
// Read the JSON file
jsonFile, err := ioutil.ReadFile(path)
if err != nil {
log.Printf("Error reading TF State File: %v", err)
return nil
}
// Parse JSON
var jsonData map[string]interface{}
err = json.Unmarshal(jsonFile, &jsonData)
if err != nil {
log.Printf("Error parsing TF State File: %v", err)
return nil
}
names := extractResourceTypes(jsonData)
return names
}
func extractResourceTypes(data map[string]interface{}) []string {
var resourceTypesFromTf []string
resources, ok := data["resources"].([]interface{})
if !ok {
log.Printf("Error: resources not found in TF State File")
return resourceTypesFromTf
}
for _, resource := range resources {
resourceMap, ok := resource.(map[string]interface{})
if !ok {
log.Printf("Error: invalid resource format in TF State File")
continue
}
resourceType, ok := resourceMap["type"].(string)
if !ok {
log.Printf("Error: Type attribute not found in resource %v", resource)
continue
}
name, ok := resourceMap["name"].(string)
if !ok {
log.Printf("Error: name attribute not found in resource %v", resource)
continue
}
resourceTypesFromTf = append(resourceTypesFromTf, resourceType+"."+name)
}
return resourceTypesFromTf
}
package tfexporter
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util/files"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
/*
This files contains all of the code used to create an export's Terraform state file. The TFStateFileWriter struct encapsulates all the logic to write a Terraform state file.
The other functions in this file deal with how to generate the TFVars we create during the export.
*/
type TFStateFileWriter struct {
ctx context.Context
resources []resourceExporter.ResourceInfo
d *schema.ResourceData
providerSource string
}
func NewTFStateWriter(ctx context.Context, resources []resourceExporter.ResourceInfo, d *schema.ResourceData, providerSource string) *TFStateFileWriter {
tfwriter := &TFStateFileWriter{
ctx: ctx,
resources: resources,
d: d,
providerSource: providerSource,
}
return tfwriter
}
func (t *TFStateFileWriter) writeTfState() diag.Diagnostics {
stateFilePath, diagErr := getFilePath(t.d, defaultTfStateFile)
if diagErr != nil {
return diagErr
}
tfstate := terraform.NewState()
for _, resource := range t.resources {
resourceState := &terraform.ResourceState{
Type: resource.Type,
Primary: resource.State,
Provider: "provider.genesyscloud",
}
tfstate.RootModule().Resources[resource.Type+"."+resource.Name] = resourceState
}
data, err := json.MarshalIndent(tfstate, "", " ")
if err != nil {
return diag.Errorf("Failed to encode state as JSON: %v", err)
}
log.Printf("Writing export state file to %s", stateFilePath)
if err := files.WriteToFile(data, stateFilePath); err != nil {
return err
}
// This outputs terraform state v3, and there is currently no public lib to generate v4 which is required for terraform 0.13+.
// However, the state can be upgraded automatically by calling the terraform CLI. If this fails, just print a warning indicating
// that the state likely needs to be upgraded manually.
cliError := `Failed to run the terraform CLI to upgrade the generated state file.
The generated tfstate file will need to be upgraded manually by running the
following in the state file's directory:
'terraform state replace-provider registry.terraform.io/-/genesyscloud registry.terraform.io/mypurecloud/genesyscloud'`
tfpath, err := exec.LookPath("terraform")
if err != nil {
log.Println("Failed to find terraform path:", err)
log.Println(cliError)
return nil
}
// exec.CommandContext does not auto-resolve symlinks
fileInfo, err := os.Lstat(tfpath)
if err != nil {
log.Println("Failed to Lstat terraform path:", err)
log.Println(cliError)
return nil
}
if fileInfo.Mode()&os.ModeSymlink != 0 {
tfpath, err = filepath.EvalSymlinks(tfpath)
if err != nil {
log.Println("Failed to resolve terraform path symlink:", err)
log.Println(cliError)
return nil
}
}
cmd := exec.CommandContext(t.ctx, tfpath)
cmd.Args = append(cmd.Args, []string{
"state",
"replace-provider",
"-auto-approve",
"-state=" + stateFilePath,
"registry.terraform.io/-/genesyscloud",
t.providerSource,
}...)
log.Printf("Running 'terraform state replace-provider' on %s", stateFilePath)
if err = cmd.Run(); err != nil {
log.Println("Failed to run command:", err)
log.Println(cliError)
return nil
}
return nil
}
func generateTfVarsContent(vars map[string]interface{}) string {
tfVarsContent := ""
for k, v := range vars {
vStr := v
if v == nil {
vStr = "null"
} else if s, ok := v.(string); ok {
vStr = fmt.Sprintf(`"%s"`, s)
} else if m, ok := v.(map[string]interface{}); ok {
vStr = fmt.Sprintf(`{
%s
}`, strings.Replace(generateTfVarsContent(m), "\n", "\n\t", -1))
}
newLine := ""
if tfVarsContent != "" {
newLine = "\n"
}
tfVarsContent = fmt.Sprintf("%v%s%s = %v", tfVarsContent, newLine, k, vStr)
}
return tfVarsContent
}
func writeTfVars(tfVars map[string]interface{}, path string) diag.Diagnostics {
tfVarsStr := generateTfVarsContent(tfVars)
tfVarsStr = fmt.Sprintf("// This file has been autogenerated. The following properties could not be retrieved from the API or would not make sense in a different org e.g. Edge IDs"+
"\n// The variables contained in this file have been given default values and should be edited as necessary\n\n%s", tfVarsStr)
log.Printf("Writing export tfvars file to %s", path)
return files.WriteToFile([]byte(tfVarsStr), path)
}
package user_roles
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *userRolesProxy
type getUserRolesByIdFunc func(ctx context.Context, p *userRolesProxy, roleId string) (*[]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error)
type updateUserRolesFunc func(ctx context.Context, p *userRolesProxy, roleId string, rolesConfig *schema.Set, subjectType string) (*platformclientv2.APIResponse, error)
type userRolesProxy struct {
clientConfig *platformclientv2.Configuration
authorizationApi *platformclientv2.AuthorizationApi
getUserRolesByIdAttr getUserRolesByIdFunc
updateUserRolesAttr updateUserRolesFunc
}
func newUserRolesProxy(clientConfig *platformclientv2.Configuration) *userRolesProxy {
api := platformclientv2.NewAuthorizationApiWithConfig(clientConfig)
return &userRolesProxy{
clientConfig: clientConfig,
authorizationApi: api,
getUserRolesByIdAttr: getUserRolesByIdFn,
updateUserRolesAttr: updateUserRolesFn,
}
}
func getUserRolesProxy(clientConfig *platformclientv2.Configuration) *userRolesProxy {
if internalProxy == nil {
internalProxy = newUserRolesProxy(clientConfig)
}
return internalProxy
}
func (p *userRolesProxy) getUserRolesById(ctx context.Context, roleId string) (*[]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error) {
return p.getUserRolesByIdAttr(ctx, p, roleId)
}
func (p *userRolesProxy) updateUserRoles(ctx context.Context, roleID string, rolesConfig *schema.Set, subjectType string) (*platformclientv2.APIResponse, error) {
return p.updateUserRolesAttr(ctx, p, roleID, rolesConfig, subjectType)
}
func getUserRolesByIdFn(_ context.Context, p *userRolesProxy, roleId string) (*[]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error) {
var grants []platformclientv2.Authzgrant
subject, resp, err := p.authorizationApi.GetAuthorizationSubject(roleId, true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get current grants for subject %s: %s", roleId, err)
}
if subject != nil && subject.Grants != nil {
for _, grant := range *subject.Grants {
if grant.SubjectId != nil && *grant.SubjectId == roleId {
grants = append(grants, grant)
}
}
}
if err != nil {
return nil, resp, err
}
return &grants, resp, nil
}
func updateUserRolesFn(_ context.Context, p *userRolesProxy, roleId string, rolesConfig *schema.Set, subjectType string) (*platformclientv2.APIResponse, error) {
// Get existing roles/divisions
subject, resp, err := p.authorizationApi.GetAuthorizationSubject(roleId, true)
grants, _, err := getAssignedGrants(*subject.Id, p)
existingGrants, configGrants, _ := getExistingAndConfigGrants(grants, rolesConfig)
if err != nil {
return resp, fmt.Errorf("failed to get current grants for subject %s: %s", roleId, err)
}
if subject != nil && subject.Grants != nil {
for _, grant := range *subject.Grants {
if grant.SubjectId != nil && *grant.SubjectId == roleId {
grants = append(grants, grant)
}
}
}
grantsToRemove, grantsToAdd := getGrantsToAddAndRemove(existingGrants, configGrants)
if len(grantsToRemove) > 0 {
// It's possible for a role or division to be removed before this update is processed,
// and the bulk remove API returns failure if any roles/divisions no longer exist.
// Work around by removing all grants individually and ignore 404s.
sdkGrantsToRemove := roleDivPairsToGrants(grantsToRemove)
for _, grant := range *sdkGrantsToRemove.Grants {
resp, err := p.authorizationApi.DeleteAuthorizationSubjectDivisionRole(roleId, *grant.DivisionId, *grant.RoleId)
if err != nil {
if resp == nil || resp.StatusCode != 404 {
return resp, fmt.Errorf("failed to remove role grants for subject %s: %s", roleId, err)
}
}
}
}
if len(grantsToAdd) > 0 {
// In some cases new roles or divisions have not yet been added to the auth service cache causing 404s that should be retried.
diagErr := util.RetryWhen(util.IsStatus404, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
resp, err := p.authorizationApi.PostAuthorizationSubjectBulkadd(roleId, roleDivPairsToGrants(grantsToAdd), subjectType)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to add role grants for subject %s error: %s", roleId, err), resp)
}
return nil, nil
})
if diagErr != nil {
return resp, fmt.Errorf("error in adding grants: %v", diagErr)
}
}
return resp, nil
}
func getAssignedGrants(subjectID string, p *userRolesProxy) ([]platformclientv2.Authzgrant, *platformclientv2.APIResponse, error) {
var grants []platformclientv2.Authzgrant
subject, resp, err := p.authorizationApi.GetAuthorizationSubject(subjectID, true)
if err != nil {
return nil, resp, fmt.Errorf("failed to get current grants for subject %s: %s", subjectID, err)
}
if subject != nil && subject.Grants != nil {
for _, grant := range *subject.Grants {
if grant.SubjectId != nil && *grant.SubjectId == subjectID {
grants = append(grants, grant)
}
}
}
return grants, resp, nil
}
package user_roles
import (
"context"
"fmt"
"log"
"strings"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
/*
The resource_genesyscloud_user_roles.go contains all the methods that perform the core logic for a resource
*/
func createUserRoles(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
userID := d.Get("user_id").(string)
d.SetId(userID)
log.Printf("Creating roles for user %s", d.Id())
return updateUserRoles(ctx, d, meta)
}
func readUserRoles(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getUserRolesProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceUserRoles(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading roles for user %s", d.Id())
d.Set("user_id", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
roles, resp, err := flattenSubjectRoles(d, proxy)
if err != nil {
if util.IsStatus404ByInt(resp.StatusCode) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read roles for user %s | error: %v", d.Id(), err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read roles for user %s | error: %v", d.Id(), err), resp))
}
_ = d.Set("roles", roles)
log.Printf("Read roles for user %s", d.Id())
return cc.CheckState(d)
})
}
func updateUserRoles(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getUserRolesProxy(sdkConfig)
if !d.HasChange("roles") {
return nil
}
rolesConfig := d.Get("roles").(*schema.Set)
if rolesConfig == nil {
return nil
}
log.Printf("Updating roles for user %s", d.Id())
resp, diagErr := proxy.updateUserRoles(ctx, d.Id(), rolesConfig, "PC_USER")
if diagErr != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update user roles %s error: %s", d.Id(), diagErr), resp)
}
log.Printf("Updated user roles for %s", d.Id())
time.Sleep(4 * time.Second)
return readUserRoles(ctx, d, meta)
}
func deleteUserRoles(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
// Does not delete users or roles. This resource will just no longer manage roles.
return nil
}
func GenerateUserRoles(resourceID string, userResource string, roles ...string) string {
return fmt.Sprintf(`resource "genesyscloud_user_roles" "%s" {
user_id = genesyscloud_user.%s.id
%s
}
`, resourceID, userResource, strings.Join(roles, "\n"))
}
package user_roles
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_user_roles"
// SetRegistrar registers all the resources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterResource(resourceName, ResourceUserRoles())
l.RegisterExporter(resourceName, UserRolesExporter())
}
var (
RoleAssignmentResource = &schema.Resource{
Schema: map[string]*schema.Schema{
"role_id": {
Description: "Role ID.",
Type: schema.TypeString,
Required: true,
},
"division_ids": {
Description: "Division IDs applied to this resource. If not set, the home division will be used. '*' may be set for all divisions.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
)
// ResourceUserRoles registers the genesyscloud_user_roles resource with terraform
func ResourceUserRoles() *schema.Resource {
return &schema.Resource{
Description: `Genesys Cloud User Roles maintains user role assignments.
Terraform expects to manage the resources that are defined in its stack. You can use this resource to assign roles to existing users that are not managed by Terraform. However, one thing you have to remember is that when you use this resource to assign roles to existing users, you must define all roles assigned to those users in this resource. Otherwise, you will inadvertently drop all of the existing roles assigned to the user and replace them with the one defined in this resource. Keep this in mind, as the author of this note inadvertently stripped his Genesys admin account of administrator privileges while using this resource to assign a role to his account. The best lessons in life are often free and self-inflicted.`,
CreateContext: provider.CreateWithPooledClient(createUserRoles),
ReadContext: provider.ReadWithPooledClient(readUserRoles),
UpdateContext: provider.UpdateWithPooledClient(updateUserRoles),
DeleteContext: provider.DeleteWithPooledClient(deleteUserRoles),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"user_id": {
Description: "User ID that will be managed by this resource. Changing the user_id attribute will cause the roles object to be dropped and recreated with a new ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"roles": {
Description: "Roles and their divisions assigned to this user.",
Type: schema.TypeSet,
Optional: true,
Elem: RoleAssignmentResource,
},
},
}
}
// userRolesExporter returns the resourceExporter object used to hold the genesyscloud_user_roles exporter's config
func UserRolesExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(genesyscloud.GetAllUsers),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"user_id": {RefType: "genesyscloud_user"},
"roles.role_id": {RefType: "genesyscloud_auth_role"},
"roles.division_ids": {RefType: "genesyscloud_auth_division", AltValues: []string{"*"}},
},
RemoveIfMissing: map[string][]string{
"roles": {"role_id"},
},
}
}
package user_roles
import (
"fmt"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func flattenSubjectRoles(d *schema.ResourceData, p *userRolesProxy) (*schema.Set, *platformclientv2.APIResponse, error) {
grants, resp, diagErr := getAssignedGrants(d.Id(), p)
if diagErr != nil {
return nil, resp, fmt.Errorf("error getting assigned grants %s", diagErr)
}
homeDivId, err := util.GetHomeDivisionID()
if err != nil {
return nil, nil, fmt.Errorf("error getting home division id %v", err)
}
roleDivsMap := make(map[string]*schema.Set)
for _, grant := range grants {
if currentDivs, ok := roleDivsMap[*grant.Role.Id]; ok {
currentDivs.Add(*grant.Division.Id)
} else {
roleDivsMap[*grant.Role.Id] = schema.NewSet(schema.HashString, []interface{}{*grant.Division.Id})
}
}
roleSet := schema.NewSet(schema.HashResource(RoleAssignmentResource), []interface{}{})
for roleID, divs := range roleDivsMap {
role := make(map[string]interface{})
role["role_id"] = roleID
role["division_ids"] = addDivisionIdsSetToRole(d, divs, roleID, homeDivId)
roleSet.Add(role)
}
return roleSet, resp, nil
}
func roleDivPairsToGrants(grantPairs []string) platformclientv2.Roledivisiongrants {
grants := make([]platformclientv2.Roledivisionpair, len(grantPairs))
for i, pair := range grantPairs {
roleDiv := strings.Split(pair, ":")
grants[i] = platformclientv2.Roledivisionpair{
RoleId: &roleDiv[0],
DivisionId: &roleDiv[1],
}
}
return platformclientv2.Roledivisiongrants{
Grants: &grants,
}
}
func addDivisionIdsSetToRole(d *schema.ResourceData, divIdsFromApi *schema.Set, roleId, homeDivId string) *schema.Set {
rolesSet, ok := d.Get("roles").(*schema.Set)
if !ok {
return divIdsFromApi
}
rolesMaps := rolesSet.List()
for _, role := range rolesMaps {
roleMap, ok := role.(map[string]interface{})
// find the role in question
if !ok || roleMap["role_id"].(string) != roleId {
continue
}
divs := roleMap["division_ids"].(*schema.Set)
for _, div := range divs.List() {
// home division id was included in original config -> use division_ids read from API
if div.(string) == homeDivId {
return divIdsFromApi
}
}
// home division ID was not included in original config for this role -> keep it out
divIdsFromApi.Remove(homeDivId)
break
}
return divIdsFromApi
}
// getExistingAndConfigGrants is used to generate the existing and config grants for the resource
func getExistingAndConfigGrants(grants []platformclientv2.Authzgrant, rolesConfig *schema.Set) ([]string, []string, error) {
rolesList := rolesConfig.List()
var existingGrants []string
for _, grant := range grants {
existingGrants = append(existingGrants, createRoleDivisionPair(*grant.Role.Id, *grant.Division.Id))
}
var configGrants []string
homeDiv, err := util.GetHomeDivisionID()
if err != nil {
return nil, nil, fmt.Errorf("failed to get home division ID %v", err)
}
for _, configRole := range rolesList {
roleMap := configRole.(map[string]interface{})
roleID := roleMap["role_id"].(string)
var divisionIDs []string
if configDivs, ok := roleMap["division_ids"].(*schema.Set); ok {
divisionIDs = *lists.SetToStringList(configDivs)
}
if len(divisionIDs) == 0 {
// No division set. Use the home division
divisionIDs = []string{homeDiv}
}
for _, divID := range divisionIDs {
configGrants = append(configGrants, createRoleDivisionPair(roleID, divID))
}
}
if err != nil {
return nil, nil, fmt.Errorf("failed to load grants: %v", err)
}
return existingGrants, configGrants, nil
}
func getGrantsToAddAndRemove(existingGrants []string, configGrants []string) ([]string, []string) {
grantsToRemove := lists.SliceDifference(existingGrants, configGrants)
grantsToAdd := lists.SliceDifference(configGrants, existingGrants)
return grantsToRemove, grantsToAdd
}
func createRoleDivisionPair(roleID string, divisionID string) string {
return roleID + ":" + divisionID
}
package chunks
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
u "github.com/rjNemo/underscore"
)
func seqGen() func() int {
seq := -1 // Initialize sequence
return func() int {
seq++
return seq
}
}
// Generic function to chunk given Items based on the size provided.
func ChunkBy[T any](items []T, chunkSize int) (chunks [][]T) {
for chunkSize < len(items) {
items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
}
return append(chunks, items)
}
// Generic function to Map each Item in Items based on a transform/builder function
func mapItems[T, P any](items []T, mapBuilder func(T) P) []P {
return u.Map(items, func(item T) P {
return mapBuilder(item)
})
}
// Generic function that Chunks the Items and then Maps
func ChunkItems[T, P any](items []T, mapBuilder func(T) P, chunkSize int) (chunks [][]P) {
mappedItems := mapItems(items, mapBuilder)
return ChunkBy(mappedItems, chunkSize)
}
// Generic function that takes array of Chunks and then Processes each Chunk with the defined Funcion.
// Typically Processor Function would be a REST API call
func ProcessChunks[T any](chunks []T, chunkProcessor func(T) diag.Diagnostics) diag.Diagnostics {
var err diag.Diagnostics
u.Map(chunks, func(chunk T) diag.Diagnostics {
if err != nil {
return err
}
err = chunkProcessor(chunk)
return err
})
return err
}
package files
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"net/url"
"os"
"path"
"strings"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
)
type S3Uploader struct {
reader io.Reader
formData map[string]io.Reader
bodyBuf *bytes.Buffer
writer *multipart.Writer
substitutions map[string]interface{}
headers map[string]string
httpMethod string
presignedUrl string
client http.Client
UploadFunc func(s *S3Uploader) ([]byte, error)
UploadWithRetriesFunc func(ctx context.Context, s *S3Uploader, filePath string, timeout time.Duration) ([]byte, error)
}
func NewS3Uploader(reader io.Reader, formData map[string]io.Reader, substitutions map[string]interface{}, headers map[string]string, method, presignedUrl string) *S3Uploader {
c := &http.Client{}
var bodyBuf bytes.Buffer
writer := multipart.NewWriter(&bodyBuf)
s3Uploader := &S3Uploader{
reader: reader,
formData: formData,
bodyBuf: &bodyBuf,
writer: writer,
substitutions: substitutions,
headers: headers,
httpMethod: method,
presignedUrl: presignedUrl,
client: *c,
UploadFunc: UploadFn,
UploadWithRetriesFunc: UploadWithRetriesFn,
}
return s3Uploader
}
func (s *S3Uploader) substituteValues() {
// Attribute specific to the flows resource
if s.substitutions != nil && len(s.substitutions) > 0 {
fileContents := s.bodyBuf.String()
for k, v := range s.substitutions {
fileContents = strings.Replace(fileContents, fmt.Sprintf("{{%s}}", k), v.(string), -1)
}
s.bodyBuf.Reset()
s.bodyBuf.WriteString(fileContents)
}
}
func (s *S3Uploader) Upload() ([]byte, error) {
return s.UploadFunc(s)
}
func (s *S3Uploader) UploadWithRetries(ctx context.Context, filePath string, timeout time.Duration) ([]byte, error) {
return s.UploadWithRetriesFunc(ctx, s, filePath, timeout)
}
func UploadFn(s *S3Uploader) ([]byte, error) {
if s.formData != nil && len(s.formData) > 0 {
if err := s.createFormData(); err != nil {
return nil, err
}
s.headers["Content-Type"] = s.writer.FormDataContentType()
} else {
_, err := io.Copy(s.bodyBuf, s.reader)
if err != nil {
return nil, fmt.Errorf("failed to copy file content to the handler. Error: %s ", err)
}
}
s.substituteValues()
req, _ := http.NewRequest(s.httpMethod, s.presignedUrl, s.bodyBuf)
for key, value := range s.headers {
req.Header.Set(key, value)
}
resp, err := s.client.Do(req)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("failed to upload file to S3 bucket with an error. Error: %s", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to upload file to S3 bucket with an HTTP status code of %d", resp.StatusCode)
}
response, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body when uploading file. %s", err)
}
return response, nil
}
func UploadWithRetriesFn(ctx context.Context, s *S3Uploader, filePath string, timeout time.Duration) ([]byte, error) {
var response []byte
fileInfo, err := os.Stat(filePath)
if err != nil {
log.Printf("failed to read file information. Path: '%s' Error: %v", filePath, err)
}
uploadErr := util.WithRetries(ctx, timeout, func() *retry.RetryError {
uploadStartTime := time.Now()
response, err = s.Upload()
if err != nil {
uploadDuration := time.Since(uploadStartTime)
log.Printf("failed to upload file %s after %d milliseconds (%v seconds). Error: %v", filePath, uploadDuration.Milliseconds(), uploadDuration.Seconds(), err)
if fileInfo != nil {
log.Printf("size of file '%s': %v bytes", filePath, fileInfo.Size())
}
return retry.RetryableError(err)
}
return nil
})
if uploadErr != nil {
return nil, fmt.Errorf("%v", uploadErr)
}
return response, nil
}
func (s *S3Uploader) createFormData() error {
defer s.writer.Close()
for key, r := range s.formData {
var (
fw io.Writer
err error
)
if r == nil {
continue
}
if x, ok := r.(io.Closer); ok {
defer x.Close()
}
if file, ok := r.(*os.File); ok {
fw, err = s.writer.CreateFormFile(key, file.Name())
} else {
fw, err = s.writer.CreateFormField(key)
}
if err != nil {
return err
}
if _, err := io.Copy(fw, r); err != nil {
return err
}
}
return nil
}
func DownloadOrOpenFile(path string) (io.Reader, *os.File, error) {
var reader io.Reader
var file *os.File
_, err := os.Stat(path)
if err != nil {
_, err = url.ParseRequestURI(path)
if err == nil {
resp, err := http.Get(path)
if err != nil {
return nil, nil, err
}
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return nil, nil, fmt.Errorf("HTTP Error downloading file: %v", resp.StatusCode)
}
reader = resp.Body
} else {
return nil, nil, fmt.Errorf("invalid file path or URL: %v", path)
}
} else {
file, err = os.Open(path)
if err != nil {
return nil, nil, err
}
reader = file
}
return reader, file, nil
}
// DownloadExportFile Download file from uri to directory/fileName
func DownloadExportFile(directory, fileName, uri string) error {
resp, err := http.Get(uri)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(path.Join(directory, fileName))
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
// Hash file content, used in stateFunc for "filepath" type attributes
func hashFileContent(path string) string {
reader, file, err := DownloadOrOpenFile(path)
if err != nil {
return err.Error()
}
if file != nil {
defer file.Close()
}
hash := sha256.New()
if file == nil {
if _, err := io.Copy(hash, reader); err != nil {
return err.Error()
}
} else {
if _, err := io.Copy(hash, file); err != nil {
return err.Error()
}
}
return hex.EncodeToString(hash.Sum(nil))
}
// Read and upload input file path to S3 pre-signed URL
func prepareAndUploadFile(filename string, substitutions map[string]interface{}, headers map[string]string, presignedUrl string) ([]byte, error) {
bodyBuf := &bytes.Buffer{}
reader, file, err := DownloadOrOpenFile(filename)
if err != nil {
return nil, err
}
if file != nil {
defer file.Close()
}
_, err = io.Copy(bodyBuf, reader)
if err != nil {
return nil, fmt.Errorf("Failed to copy file content to the handler. Error: %s ", err)
}
// Attribute specific to the flows resource
if len(substitutions) > 0 {
fileContents := bodyBuf.String()
for k, v := range substitutions {
fileContents = strings.Replace(fileContents, fmt.Sprintf("{{%s}}", k), v.(string), -1)
}
bodyBuf.Reset()
bodyBuf.WriteString(fileContents)
}
req, _ := http.NewRequest("PUT", presignedUrl, bodyBuf)
for key, value := range headers {
req.Header.Set(key, value)
}
client := &http.Client{}
resp, err := client.Do(req)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil || resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Failed to upload file to S3 bucket. Error: %s ", err)
}
response, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Failed to read response body when uploading file. %s", err)
}
return response, nil
}
func WriteToFile(bytes []byte, path string) diag.Diagnostics {
err := os.WriteFile(path, bytes, os.ModePerm)
if err != nil {
return util.BuildDiagnosticError("File Writer", fmt.Sprintf("Error writing file with Path %s", path), err)
}
return nil
}
package lists
func ChunkStringSlice(slice []string, chunkSize int) [][]string {
var chunks [][]string
for i := 0; i < len(slice); i += chunkSize {
end := i + chunkSize
// check to avoid slicing beyond slice capacity
if end > len(slice) {
end = len(slice)
}
chunks = append(chunks, slice[i:end])
}
return chunks
}
package lists
import (
"sort"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func ItemInSlice[T comparable](a T, list []T) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func RemoveStringFromSlice(value string, slice []string) []string {
s := make([]string, 0)
for _, v := range slice {
if v != value {
s = append(s, v)
}
}
return s
}
func SubStringInSlice(a string, list []string) bool {
for _, b := range list {
if strings.Contains(b, a) {
return true
}
}
return false
}
// SliceDifference returns the elements in a that aren't in b
func SliceDifference(a, b []string) []string {
var diff []string
if len(a) == 0 {
return diff
}
mb := make(map[string]struct{}, len(b))
for _, x := range b {
mb[x] = struct{}{}
}
for _, x := range a {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return diff
}
// AreEquivalent takes two string lists and returns true if they are equivalent, ignoring the ordering of the items.
func AreEquivalent(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
aCopy := make([]string, len(a))
copy(aCopy, a)
bCopy := make([]string, len(b))
copy(bCopy, b)
sort.Strings(aCopy)
sort.Strings(bCopy)
for i := 0; i < len(aCopy); i++ {
if aCopy[i] != bCopy[i] {
return false
}
}
return true
}
func StringListToSet(list []string) *schema.Set {
interfaceList := make([]interface{}, len(list))
for i, v := range list {
interfaceList[i] = v
}
return schema.NewSet(schema.HashString, interfaceList)
}
func StringListToSetOrNil(list *[]string) *schema.Set {
if list == nil {
return nil
}
return StringListToSet(*list)
}
func StringListToInterfaceList(list []string) []interface{} {
interfaceList := make([]interface{}, len(list))
for i, v := range list {
interfaceList[i] = v
}
return interfaceList
}
func SetToStringList(strSet *schema.Set) *[]string {
interfaceList := strSet.List()
strList := InterfaceListToStrings(interfaceList)
return &strList
}
func InterfaceListToStrings(interfaceList []interface{}) []string {
strs := make([]string, len(interfaceList))
for i, val := range interfaceList {
strs[i] = val.(string)
}
return strs
}
func BuildSdkStringList(d *schema.ResourceData, attrName string) *[]string {
if val, ok := d.GetOk(attrName); ok {
return SetToStringList(val.(*schema.Set))
}
return nil
}
func BuildSdkStringListFromInterfaceArray(d *schema.ResourceData, attrName string) *[]string {
var stringArray []string
if val, ok := d.GetOk(attrName); ok {
if valArray, ok := val.([]interface{}); ok {
stringArray = InterfaceListToStrings(valArray)
}
}
return &stringArray
}
func FlattenList[T interface{}](resourceList *[]T, elementFlattener func(resource *T) map[string]interface{}) *[]map[string]interface{} {
if resourceList == nil {
return nil
}
var resultList []map[string]interface{}
for _, resource := range *resourceList {
resultList = append(resultList, elementFlattener(&resource))
}
return &resultList
}
func FlattenAsList[T interface{}](resource *T, elementFlattener func(resource *T) map[string]interface{}) *[]map[string]interface{} {
if resource == nil {
return nil
}
flattened := elementFlattener(resource)
if flattened != nil {
return &[]map[string]interface{}{flattened}
}
return nil
}
func NilToEmptyList[T interface{}](list *[]T) *[]T {
if list == nil {
emptyArray := []T{}
return &emptyArray
}
return list
}
// Remove an item from a string list based on the value
func Remove[T comparable](s []T, r T) []T {
for i, v := range s {
if v == r {
return append(s[:i], s[i+1:]...)
}
}
return s
}
package stringmap
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func GetNillableValue[T any](m map[string]interface{}, key string) *T {
value, ok := m[key]
if ok {
v := value.(T)
return &v
}
return nil
}
func GetNonDefaultValue[T comparable](m map[string]interface{}, key string) *T {
value := GetNillableValue[T](m, key)
if value != nil {
defaultValue := new(T)
if *value != *defaultValue {
return value
}
}
return nil
}
func SetValueIfNotNil[T any](m map[string]interface{}, key string, value *T) {
if value != nil {
m[key] = *value
}
}
func BuildSdkStringList(m map[string]interface{}, key string) *[]string {
return BuildSdkList[string](m, key, nil)
}
func BuildSdkListFirstElement[T interface{}](m map[string]interface{}, key string, elementBuilder func(map[string]interface{}) *T, nilForEmpty bool) *T {
list := m[key].(*schema.Set).List()
if len(list) > 0 {
return elementBuilder(list[0].(map[string]interface{}))
}
if nilForEmpty {
return nil
}
return elementBuilder(nil)
}
func BuildSdkList[T interface{}](m map[string]interface{}, key string, elementBuilder func(map[string]interface{}) *T) *[]T {
child := m[key]
if child != nil {
list := child.(*schema.Set).List()
sdkList := make([]T, len(list))
for i, element := range list {
switch element.(type) {
case T:
sdkList[i] = element.(T)
case map[string]interface{}:
sdkList[i] = *elementBuilder(element.(map[string]interface{}))
}
}
return &sdkList
}
return nil
}
func MergeMaps[T, U comparable](m1, m2 map[T][]U) map[T][]U {
result := make(map[T][]U)
for key, value := range m1 {
result[key] = value
}
for key, value := range m2 {
result[key] = value
}
return result
}
func MergeSingularMaps[T, U comparable](m1, m2 map[T]U) map[T]U {
result := make(map[T]U)
for key, value := range m1 {
result[key] = value
}
for key, value := range m2 {
result[key] = value
}
return result
}
package util
import (
"encoding/json"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"testing"
"time"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
NullValue = "null"
TrueValue = "true"
FalseValue = "false"
TestCert1 = "MIIDazCCAlKgAwIBAgIBADANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJ1czEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAoMB0dlbmVzeXMxFDASBgNVBAMMC215cHVyZWNsb3VkMCAXDTIyMDUxNzEzNDUzM1oYDzIxMjIwNDIzMTM0NTMzWjBOMQswCQYDVQQGEwJ1czEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAoMB0dlbmVzeXMxFDASBgNVBAMMC215cHVyZWNsb3VkMIIBIzANBgkqhkiG9w0BAQEFAAOCARAAMIIBCwKCAQIAuicPlCgrmmzIuu/Hh0HBqmGOvO7lLeKq4ZryZxd11XmcVE4T4mhdI+u1rgv8GBnn9JmFkXGU793l1PuUmrZuUInkuvVhvOjcl/95WzGE5++bkvQ/AhROn4onAWQIrQvpUq+xKv3vZ4z7JncqbkBRsJ1BKsCxtL3nKLlUBD2z8/KrrbKjENEDCIlhdua5KPfl/d+IwW8iOmTsLQYNsSv8ZvovwK/WwvcFsjtQIdBSdJfPguAzKiQIaihzya6dzXLFlxYsBsbA39MEcNTeOpy+b1xNEo0WCvVW0qctVV+z3qHMHqcjkikT4PUzBkeceZe5dnqfm+P1TFTk1OO8b0xmkgECAwEAAaNQME4wHQYDVR0OBBYEFCuD7HIc4V8HNEAftG5w+nFFl5JVMB8GA1UdIwQYMBaAFCuD7HIc4V8HNEAftG5w+nFFl5JVMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggECAEUmWVt01Kh1Be4U+CrI8Vdz6Hls3RJmto/x0WQUARjUO3+0SiFUxFAgRGGkFJTdtH+J93OntLsK8Av+G3U+ZNCODbRBubXqcnljbXnaeXDp4saUWuRs4G6zYFPM0rCvSz46XK6G5dyANeEJFgdO7wKkHO/eyy4PkIgjBE59DAx97sbXW877DTdvSfbmsEKiuEB0an+kdPYZHbTLdM910Y8YyeEQBkzp1Kjz3u5fwpAKFULOhsBmXYtXTReMqtWHjG4czsRZr04wHIng45WD8weMdw1UsCpr8fJ4CYMJsKgwJkKOc8fw6Fmj7mqrXIlUMMpeyDNpqEMaNIryiG/UsZma"
TestCert2 = "MIIDazCCAlKgAwIBAgIBADANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJ1czEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAoMB0dlbmVzeXMxFDASBgNVBAMMC215cHVyZWNsb3VkMCAXDTIyMDUxNzEzNDY0N1oYDzIxMjIwNDIzMTM0NjQ3WjBOMQswCQYDVQQGEwJ1czEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAoMB0dlbmVzeXMxFDASBgNVBAMMC215cHVyZWNsb3VkMIIBIzANBgkqhkiG9w0BAQEFAAOCARAAMIIBCwKCAQIAzWc4XQthXrGexwsH2urKc1dFPhZMoWhUVjXrb1bc1IdCH63KklnhYiBAB2YakRJVSzoat5iY0X2kNjSIyCtHCxPycpplP4P6BfIEM9jm0s8NmYW3S/8JZW1MiNs/2XTibfyoXmQiHh76BzKCDgniulj2qOxpNHi5M1Az0QxV+GSgVE+mcPA6041idt7n1HpG3gQ7/MrZEd5OdBhyVUa6JPDyTAF7UE9P9v7mIbGoe6R7Y9qQEIbJ8ihoSM+w65fhyDafl9dWjfLmqkI65cYCJ82cGqyseeiHYOXgyfkcC1njrLr5g92DHnOVqVoHZCTzwV+kciyAntuQqyJtHGCGnskCAwEAAaNQME4wHQYDVR0OBBYEFDNbxsJcQMKJVSIHT/3BM1Osb+JOMB8GA1UdIwQYMBaAFDNbxsJcQMKJVSIHT/3BM1Osb+JOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggECAGuzz8i3w3YrFGeGgxRzwEWUKiH53Sf4w7KIxGeK6oW2BOnhXMYJfuqIAiGaAVQ3uHbTcKwByHLK9/2oWmQAsYsbA3wZpcZXyXk84iCc3aqYkWjeUl0A5wECjNIKkFvS56DCtENLMlc2VI8NGzPoFMaC7Z3nMOlogqsf6KNNydUMgqyosLQqYoRdDbBMXShbn7fvibK4jzhYxuoXCyTwKDg/lr69i5zsVNBMjTu8W3DnmBPbTVBQ9Kd9/nAJoXCbHfx1QW4UEx3mLFDVNhRRdGqran7DIEjCo8BcGilXvHCVCAKwXF1MyqiyLEm8/W7FYzdBBkkVnxOBhMIVjlPGpwLS"
)
func TestAccPreCheck(t *testing.T) {
if v := os.Getenv("GENESYSCLOUD_OAUTHCLIENT_ID"); v == "" {
t.Fatal("Missing env GENESYSCLOUD_OAUTHCLIENT_ID")
}
if v := os.Getenv("GENESYSCLOUD_OAUTHCLIENT_SECRET"); v == "" {
t.Fatal("Missing env GENESYSCLOUD_OAUTHCLIENT_SECRET")
}
if v := os.Getenv("GENESYSCLOUD_REGION"); v == "" {
os.Setenv("GENESYSCLOUD_REGION", "dca") // Default to dev environment
}
}
// For fields such as genesyscloud_outbound_campaign.campaign_status, which use a diff suppress func,
// and may return as "on", or "complete" depending on how long the operation takes
func VerifyAttributeInArrayOfPotentialValues(resource string, key string, potentialValues []string) resource.TestCheckFunc {
return func(state *terraform.State) error {
r := state.RootModule().Resources[resource]
if r == nil {
return fmt.Errorf("%s not found in state", resource)
}
a := r.Primary.Attributes
attributeValue := a[key]
for _, v := range potentialValues {
if attributeValue == v {
return nil
}
}
return fmt.Errorf(`expected %s to be one of [%s], got "%s"`, key, strings.Join(potentialValues, ", "), attributeValue)
}
}
func ValidateStringInArray(resourceName string, attrName string, value string) resource.TestCheckFunc {
return func(state *terraform.State) error {
resourceState, ok := state.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Failed to find resourceState %s in state", resourceName)
}
resourceID := resourceState.Primary.ID
numAttr, ok := resourceState.Primary.Attributes[attrName+".#"]
if !ok {
return fmt.Errorf("No %s found for %s in state", attrName, resourceID)
}
numValues, _ := strconv.Atoi(numAttr)
for i := 0; i < numValues; i++ {
if resourceState.Primary.Attributes[attrName+"."+strconv.Itoa(i)] == value {
// Found value
return nil
}
}
return fmt.Errorf("%s %s not found for group %s in state", attrName, value, resourceID)
}
}
// The 'TestCheckResourceAttrPair' version of ValidateStringInArray
func ValidateResourceAttributeInArray(resource1Name string, arrayAttrName, resource2Name string, valueAttrName string) resource.TestCheckFunc {
return func(state *terraform.State) error {
valueResourceState, ok := state.RootModule().Resources[resource2Name]
if !ok {
return fmt.Errorf("Failed to find resourceState %s in state", resource2Name)
}
resourceID := valueResourceState.Primary.ID
value, ok := valueResourceState.Primary.Attributes[valueAttrName]
if !ok {
return fmt.Errorf("No %s found for %s in state", valueAttrName, resourceID)
}
arrayResourceState, ok := state.RootModule().Resources[resource1Name]
if !ok {
return fmt.Errorf("Failed to find resourceState %s in state", resource1Name)
}
resource2ID := arrayResourceState.Primary.ID
numAttr, ok := arrayResourceState.Primary.Attributes[arrayAttrName+".#"]
if !ok {
return fmt.Errorf("No %s found for %s in state", arrayAttrName, resource2ID)
}
numValues, _ := strconv.Atoi(numAttr)
for i := 0; i < numValues; i++ {
if arrayResourceState.Primary.Attributes[arrayAttrName+"."+strconv.Itoa(i)] == value {
// Found value
return nil
}
}
return fmt.Errorf("%s %s not found for group %s in state", arrayAttrName, value, resourceID)
}
}
func StrArrayEquals(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func ValidateValueInJsonAttr(resourceName string, attrName string, jsonProp string, jsonValue string) resource.TestCheckFunc {
return func(state *terraform.State) error {
resourceState, ok := state.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Failed to find resource %s in state", resourceName)
}
resourceID := resourceState.Primary.ID
jsonAttr, ok := resourceState.Primary.Attributes[attrName]
if !ok {
return fmt.Errorf("No %s found for %s in state", attrName, resourceID)
}
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(jsonAttr), &jsonMap); err != nil {
return fmt.Errorf("Error parsing JSON for %s in state: %v", resourceID, err)
}
propPath := strings.Split(jsonProp, ".")
if val, ok := jsonMap[propPath[0]]; ok {
for i := 1; i < len(propPath); i++ {
switch obj := val.(type) {
case map[string]interface{}:
val = obj[propPath[i]]
case []interface{}:
val = obj
default:
return fmt.Errorf("JSON property %s not found for %s in state", jsonProp, resourceID)
}
}
if arr, ok := val.([]interface{}); ok {
// Property is an array. Check if string value exists in array.
if lists.ItemInSlice(jsonValue, lists.InterfaceListToStrings(arr)) {
return nil
}
return fmt.Errorf("JSON array property for resourceState %s.%s does not contain expected %s", resourceName, jsonProp, jsonValue)
} else {
strVal := interfaceToString(val)
if strVal != jsonValue {
return fmt.Errorf("JSON property for resource %s %s=%s does not match expected %s", resourceName, jsonProp, strVal, jsonValue)
}
}
} else {
return fmt.Errorf("JSON property %s not found for %s in state", jsonProp, resourceID)
}
return nil
}
}
func ValidateValueInJsonPropertiesAttr(resourceName string, attrName string, jsonProp string, jsonValue string) resource.TestCheckFunc {
return func(state *terraform.State) error {
resourceState, ok := state.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Failed to find resourceState %s in state", resourceName)
}
resourceID := resourceState.Primary.ID
jsonAttr, ok := resourceState.Primary.Attributes[attrName]
if !ok {
return fmt.Errorf("No %s found for %s in state", attrName, resourceID)
}
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(jsonAttr), &jsonMap); err != nil {
return fmt.Errorf("Error parsing JSON for %s in state: %v", resourceID, err)
}
propPath := strings.Split(jsonProp, ".")
if val, ok := jsonMap[propPath[0]]; ok {
for i := 1; i < len(propPath); i++ {
switch obj := val.(type) {
case map[string]interface{}:
val = obj[propPath[i]]
case []interface{}:
val = obj
default:
return fmt.Errorf("JSON property %s not found for %s in state", jsonProp, resourceID)
}
}
valInstance := val.(map[string]interface{})["value"].(map[string]interface{})["instance"]
if valInstanceString, ok := valInstance.(string); ok {
if valInstanceString != jsonValue {
return fmt.Errorf("JSON property for resource %s %s=%s does not match expected %s", resourceName, jsonProp, valInstanceString, jsonValue)
}
} else if valInstanceFloat, ok := valInstance.(float64); ok {
intValue, err := strconv.Atoi(jsonValue)
if err != nil {
return err
}
if int(valInstanceFloat) != intValue {
return fmt.Errorf("JSON property for resource %s %s=%v does not match expected %v", resourceName, jsonProp, valInstanceFloat, jsonValue)
}
} else if valInstanceBool, ok := valInstance.(bool); ok {
boolValue, err := strconv.ParseBool(jsonValue)
if err != nil {
return err
}
if valInstanceBool != boolValue {
return fmt.Errorf("JSON property for resource %s %s=%v does not match expected %v", resourceName, jsonProp, valInstanceBool, jsonValue)
}
} else if valInstanceSlice, ok := valInstance.([]interface{}); ok {
if _, ok := valInstanceSlice[0].(float64); ok {
ints := make([]string, 0)
for _, i := range valInstanceSlice {
ints = append(ints, strconv.Itoa(int(i.(float64))))
}
intsJoined := strings.Join(ints, ",")
if intsJoined != jsonValue {
return fmt.Errorf("JSON property for resource %s %s=%s does not match expected %s", resourceName, jsonProp, intsJoined, jsonValue)
}
} else if _, ok := valInstanceSlice[0].(string); ok {
strs := make([]string, 0)
for _, s := range valInstanceSlice {
strs = append(strs, s.(string))
}
strsJoined := strings.Join(strs, ",")
if strsJoined != jsonValue {
return fmt.Errorf("JSON property for resource %s %s=%s does not match expected %s", resourceName, jsonProp, strsJoined, jsonValue)
}
}
}
} else {
return fmt.Errorf("JSON property %s not found for %s in state", jsonProp, resourceID)
}
return nil
}
}
func GenerateJsonEncodedProperties(properties ...string) string {
return fmt.Sprintf(`jsonencode({
%s
})
`, strings.Join(properties, "\n"))
}
func GenerateJsonProperty(propName string, propValue string) string {
return fmt.Sprintf(`"%s" = %s`, propName, propValue)
}
func GenerateJsonArrayPropertyEnquote(propName string, propValues ...string) string {
quotedVals := []string{}
for _, strv := range propValues {
quotedVals = append(quotedVals, strconv.Quote(strv))
}
return GenerateJsonArrayProperty(propName, quotedVals...)
}
func GenerateJsonArrayProperty(propName string, propValues ...string) string {
return fmt.Sprintf(`"%s" = [%s]`, propName, strings.Join(propValues, ", "))
}
func GenerateJsonObject(properties ...string) string {
return fmt.Sprintf(`{
%s
}`, strings.Join(properties, "\n"))
}
func GenerateStringArray(vals ...string) string {
return fmt.Sprintf("[%s]", strings.Join(vals, ","))
}
func GenerateStringArrayEnquote(vals ...string) string {
quotedVals := []string{}
for _, strv := range vals {
quotedVals = append(quotedVals, strconv.Quote(strv))
}
return fmt.Sprintf("[%s]", strings.Join(quotedVals, ","))
}
func GenerateMapProperty(propName string, propValue string) string {
return fmt.Sprintf(`%s = %s`, propName, propValue)
}
func GenerateMapAttr(name string, properties ...string) string {
return fmt.Sprintf(`%s = {
%s
}
`, name, strings.Join(properties, "\n"))
}
func GenerateMapAttrWithMapProperties(name string, properties map[string]string) string {
var propertiesStr string
for k, v := range properties {
propertiesStr += GenerateMapProperty(k, v) + "\n"
}
return fmt.Sprintf(`%s = {
%s
}
`, name, propertiesStr)
}
func GenerateSubstitutionsMap(substitutions map[string]string) string {
var substitutionsStr string
for k, v := range substitutions {
substitutionsStr += fmt.Sprintf("\t%s = \"%s\"\n", k, v)
}
return fmt.Sprintf(`substitutions = {
%s}`, substitutionsStr)
}
func GenerateJsonSchemaDocStr(properties ...string) string {
attrType := "type"
attrProperties := "properties"
typeObject := "object"
typeStr := "string" // All string props
propStrs := []string{}
for _, prop := range properties {
propStrs = append(propStrs, GenerateJsonProperty(prop, GenerateJsonObject(
GenerateJsonProperty(attrType, strconv.Quote(typeStr)),
)))
}
allProps := strings.Join(propStrs, "\n")
return GenerateJsonEncodedProperties(
// First field is required
GenerateJsonArrayProperty("required", strconv.Quote(properties[0])),
GenerateJsonProperty(attrType, strconv.Quote(typeObject)),
GenerateJsonProperty(attrProperties, GenerateJsonObject(
allProps,
)),
)
}
func RandString(length int) string {
rand.Seed(time.Now().UnixNano())
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
s := make([]rune, length)
for i := range s {
s[i] = letters[rand.Intn(len(letters))]
}
return string(s)
}
// Added locally to break a circular dependency
func interfaceToString(val interface{}) string {
return fmt.Sprintf("%v", val)
}
func AssignRegion() string {
region := "us-west-2"
if v := os.Getenv("GENESYSCLOUD_REGION"); v == "tca" {
region = "us-east-1"
} else if v == "us-east-1" {
region = "us-west-2"
}
regionJSON := "[" + strconv.Quote(region) + "]"
return regionJSON
}
package typeconv
import (
"fmt"
"strconv"
)
func Float32to64(float32Value *float32) *float64 {
if float32Value == nil {
return nil
}
floatString := fmt.Sprintf("%f", *float32Value)
float64Value, _ := strconv.ParseFloat(floatString, 64)
return &float64Value
}
func Float64to32(float64Value *float64) *float32 {
if float64Value == nil {
return nil
}
float32Value := float32(*float64Value)
return &float32Value
}
package util
import (
"context"
"encoding/json"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func BuildTelephonyProperties(d *schema.ResourceData) *map[string]interface{} {
returnValue := make(map[string]interface{})
if properties := d.Get("properties"); properties != nil {
inputVal, err := JsonStringToInterface(properties.(string))
if err != nil {
return nil
}
returnValue = inputVal.(map[string]interface{})
}
return &returnValue
}
func FlattenTelephonyProperties(properties interface{}) (string, diag.Diagnostics) {
if properties == nil {
return "", nil
}
propertiesBytes, err := json.Marshal(properties)
if err != nil {
return "", diag.Errorf("Error marshalling properties %v: %v", properties, err)
}
return string(propertiesBytes), nil
}
func CustomizePhoneBaseSettingsPropertiesDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
// Defaults must be set on missing properties
if !diff.NewValueKnown("properties") {
// properties value not yet in final state. Nothing to do.
return nil
}
id := diff.Id()
if id == "" {
return nil
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
// Retrieve defaults from the settings
phoneBaseSetting, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesPhonebasesetting(id)
if getErr != nil {
if IsStatus404(resp) {
return nil
}
return fmt.Errorf("failed to read phone base settings %s: %s", id, getErr)
}
return applyPropertyDefaults(diff, phoneBaseSetting.Properties)
}
func CustomizeTrunkBaseSettingsPropertiesDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
// Defaults must be set on missing properties
if !diff.NewValueKnown("properties") {
// properties value not yet in final state. Nothing to do.
return nil
}
id := diff.Id()
if id == "" {
return nil
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
// Retrieve defaults from the settings
trunkBaseSetting, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesTrunkbasesetting(id, true)
if getErr != nil {
if IsStatus404(resp) {
return nil
}
return fmt.Errorf("failed to read phone base settings %s: %s", id, getErr)
}
return applyPropertyDefaults(diff, trunkBaseSetting.Properties)
}
func applyPropertyDefaults(diff *schema.ResourceDiff, properties *map[string]interface{}) error {
// Parse resource properties into map
propertiesJson := diff.Get("properties").(string)
configMap := map[string]interface{}{}
if propertiesJson == "" {
propertiesJson = "{}" // empty object by default
}
if err := json.Unmarshal([]byte(propertiesJson), &configMap); err != nil {
return fmt.Errorf("failure to parse properties for %s: %s", diff.Id(), err)
}
// For each property in the schema, check if a value is set in the config
if properties != nil {
for name, prop := range *properties {
if _, set := configMap[name]; !set {
// Just set a default value if the property wasn't specified
configMap[name] = prop
} else {
configMapProp := configMap[name].(map[string]interface{})
// Get the instance value from the config
instance := configMapProp["value"].(map[string]interface{})["instance"]
if instance == nil {
continue
}
// Assign the property from the API to the config
configMap[name] = prop
// Overwrite the instance because that's all we need to set
configMap[name].(map[string]interface{})["value"].(map[string]interface{})["instance"] = instance
}
}
}
// Marshal back to string and set as the diff value
result, err := json.Marshal(configMap)
if err != nil {
return fmt.Errorf("failure to marshal properties for %s: %s", diff.Id(), err)
}
return diff.SetNew("properties", string(result))
}
func CustomizePhonePropertiesDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
// Defaults must be set on missing properties
if !diff.NewValueKnown("properties") {
// properties value not yet in final state. Nothing to do.
return nil
}
id := diff.Id()
if id == "" {
return nil
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig)
// Retrieve defaults from the settings
phone, resp, getErr := edgesAPI.GetTelephonyProvidersEdgesPhone(id)
if getErr != nil {
if IsStatus404(resp) {
return nil
}
return fmt.Errorf("failed to read phone %s: %s", id, getErr)
}
return applyPropertyDefaults(diff, phone.Properties)
}
package util
import (
"encoding/json"
"errors"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type detailedDiagnosticInfo struct {
ResourceName string `json:"resourceName,omitempty"`
Method string `json:"method,omitempty"`
Path string `json:"path,omitempty"`
StatusCode int `json:"statusCode,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
CorrelationID string `json:"correlationId,omitempty"`
}
func convertResponseToWrapper(resourceName string, apiResponse *platformclientv2.APIResponse) *detailedDiagnosticInfo {
return &detailedDiagnosticInfo{
ResourceName: resourceName,
Method: apiResponse.Response.Request.Method,
Path: apiResponse.Response.Request.URL.Path,
StatusCode: apiResponse.StatusCode,
ErrorMessage: apiResponse.ErrorMessage,
CorrelationID: apiResponse.CorrelationID,
}
}
func BuildAPIDiagnosticError(resourceName string, summary string, apiResponse *platformclientv2.APIResponse) diag.Diagnostics {
//Checking to make sure we have properly formed response
if apiResponse == nil || apiResponse.Response == nil || apiResponse.Response.Request == nil || apiResponse.Response.Request.URL == nil {
error := fmt.Errorf("Unable to build a message from the response because the APIResponse does not contain the appropriate data.%s", "")
return BuildDiagnosticError(resourceName, summary, error)
}
diagInfo := convertResponseToWrapper(resourceName, apiResponse)
diagInfoByte, err := json.Marshal(diagInfo)
//Checking to see if we can Marshall the data
if err != nil {
error := fmt.Errorf("Unable to unmarshal diagnostic info while building diagnostic error. Error: %s", err)
return BuildDiagnosticError(resourceName, summary, error)
}
dg := diag.Diagnostic{Severity: diag.Error, Summary: summary, Detail: string(diagInfoByte)}
var dgs diag.Diagnostics
dgs = append(dgs, dg)
return dgs
}
func BuildDiagnosticError(resourceName string, summary string, err error) diag.Diagnostics {
var msg string
diagInfo := &detailedDiagnosticInfo{
ResourceName: resourceName,
ErrorMessage: fmt.Sprintf("%s", err),
}
diagInfoByte, err := json.Marshal(diagInfo)
if err != nil {
msg = fmt.Sprintf("{'resourceName': '%s', 'details': 'Unable to unmarshal diagnostic info while building diagnostic error'}", resourceName)
} else {
msg = string(diagInfoByte)
}
dg := diag.Diagnostic{Severity: diag.Error, Summary: summary, Detail: msg}
var dgs diag.Diagnostics
dgs = append(dgs, dg)
return dgs
}
// BuildWithRetriesApiDiagnosticError converts the diag.Diagnostic error from API responses into an error to be used in withRetries functions for more clear error information
func BuildWithRetriesApiDiagnosticError(resourceName string, summary string, apiResponse *platformclientv2.APIResponse) error {
var errorMsg string
diagnostic := BuildAPIDiagnosticError(resourceName, summary, apiResponse)
for _, diags := range diagnostic {
errorMsg += fmt.Sprintf("%s\n%s\n", diags.Summary, diags.Detail)
}
return errors.New(errorMsg)
}
package util
import (
"fmt"
"log"
"sync"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
type JsonMap map[string]interface{}
// Attempt to get the home division once during a provider run
var divOnce sync.Once
var homeDivID string
var homeDivErr diag.Diagnostics
func GetHomeDivisionName(key string, divisionName *string) resource.TestCheckFunc {
return func(state *terraform.State) error {
homeDivision, ok := state.RootModule().Resources[key]
if !ok {
return fmt.Errorf("Failed to find home division")
}
*divisionName = homeDivision.Primary.Attributes["name"]
return nil
}
}
func GetHomeDivisionID() (string, diag.Diagnostics) {
divOnce.Do(func() {
authAPI := platformclientv2.NewAuthorizationApi()
homeDiv, _, err := authAPI.GetAuthorizationDivisionsHome()
if err != nil {
homeDivErr = diag.Errorf("Failed to query home division: %s", err)
return
}
homeDivID = *homeDiv.Id
})
if homeDivErr != nil {
return "", homeDivErr
}
return homeDivID, nil
}
func UpdateObjectDivision(d *schema.ResourceData, objType string, sdkConfig *platformclientv2.Configuration) diag.Diagnostics {
if d.HasChange("division_id") {
authAPI := platformclientv2.NewAuthorizationApiWithConfig(sdkConfig)
divisionID := d.Get("division_id").(string)
if divisionID == "" {
// Default to home division
homeDivision, diagErr := GetHomeDivisionID()
if diagErr != nil {
return diagErr
}
divisionID = homeDivision
}
log.Printf("Updating division for %s %s to %s", objType, d.Id(), divisionID)
_, divErr := authAPI.PostAuthorizationDivisionObject(divisionID, objType, []string{d.Id()})
if divErr != nil {
return diag.Errorf("Failed to update division for %s %s: %s", objType, d.Id(), divErr)
}
}
return nil
}
package util
import (
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func BuildSdkDomainEntityRef(d *schema.ResourceData, idAttr string) *platformclientv2.Domainentityref {
idVal := d.Get(idAttr).(string)
if idVal == "" {
return nil
}
return &platformclientv2.Domainentityref{Id: &idVal}
}
func BuildSdkDomainEntityRefArr(d *schema.ResourceData, idAttr string) *[]platformclientv2.Domainentityref {
if ids, ok := d.GetOk(idAttr); ok && ids != nil {
if setIds, ok := ids.(*schema.Set); ok {
strList := lists.SetToStringList(setIds)
if setIds != nil {
domainEntityRefs := make([]platformclientv2.Domainentityref, len(*strList))
for i, id := range *strList {
tempId := id
domainEntityRefs[i] = platformclientv2.Domainentityref{Id: &tempId}
}
return &domainEntityRefs
}
} else {
strList := lists.InterfaceListToStrings(ids.([]interface{}))
if len(strList) > 0 {
domainEntityRefs := make([]platformclientv2.Domainentityref, len(strList))
for i, id := range strList {
tempId := id
domainEntityRefs[i] = platformclientv2.Domainentityref{Id: &tempId}
}
return &domainEntityRefs
}
}
}
return nil
}
func BuildSdkDomainEntityRefArrFromArr(ids []interface{}) *[]platformclientv2.Domainentityref {
var domainEntityRefs []platformclientv2.Domainentityref
for _, id := range ids {
if idStr, ok := id.(string); ok {
domainEntityRefs = append(domainEntityRefs, platformclientv2.Domainentityref{Id: &idStr})
}
}
return &domainEntityRefs
}
func SdkDomainEntityRefArrToSet(entityRefs []platformclientv2.Domainentityref) *schema.Set {
interfaceList := make([]interface{}, len(entityRefs))
for i, v := range entityRefs {
interfaceList[i] = *v.Id
}
return schema.NewSet(schema.HashString, interfaceList)
}
func SdkDomainEntityRefArrToList(entityRefs []platformclientv2.Domainentityref) []interface{} {
interfaceList := make([]interface{}, len(entityRefs))
for i, v := range entityRefs {
interfaceList[i] = *v.Id
}
return interfaceList
}
package util
import (
"bytes"
"encoding/json"
"fmt"
"log"
"reflect"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// EquivalentJsons checks if two jsons are equivalent but has the special behavior
// where any null property in the 'incoming' json is removed(deeply) prior to deep comparison.
// Used to compare a json string from the terraform config and a json response from the API.
func EquivalentJsons(original, incoming string) bool {
if original == incoming {
return true
}
ob := bytes.NewBufferString("")
if err := json.Compact(ob, []byte(original)); err != nil {
log.Printf("error while comparing jsons: %v", err)
return false
}
nb := bytes.NewBufferString("")
if err := json.Compact(nb, []byte(incoming)); err != nil {
log.Printf("error while comparing jsons: %v", err)
return false
}
var nbi interface{}
if err := json.Unmarshal(nb.Bytes(), &nbi); err != nil {
log.Printf("error while comparing jsons: %v", err)
return false
}
jsonDeepDelNullProperties(nbi)
nbClean, err := json.Marshal(nbi)
if err != nil {
log.Printf("error while comparing jsons: %v", err)
return false
}
return jsonBytesEqual(ob.Bytes(), nbClean)
}
// SuppressDiffFunc for properties that will accept JSON strings
func SuppressEquivalentJsonDiffs(k, old, new string, d *schema.ResourceData) bool {
return EquivalentJsons(old, new)
}
// Recursively go through decoded JSON map and remove any property that is null.
// Parameter can also be an array(slice) like in JSON but will only traverse for
// further map/slice elements. ie null elements in slices are not removed.
func jsonDeepDelNullProperties(o interface{}) {
ov := reflect.ValueOf(o)
switch ov.Kind() {
case reflect.Slice:
for _, n := range o.([]any) {
jsonDeepDelNullProperties(n)
}
case reflect.Map:
for key, value := range o.(map[string]interface{}) {
if value == nil {
delete(o.(map[string]interface{}), key)
}
v1 := reflect.ValueOf(value)
if v1.Kind() == reflect.Map {
jsonDeepDelNullProperties(value)
}
}
}
}
func jsonBytesEqual(b1, b2 []byte) bool {
var o1 interface{}
if err := json.Unmarshal(b1, &o1); err != nil {
return false
}
var o2 interface{}
if err := json.Unmarshal(b2, &o2); err != nil {
return false
}
return reflect.DeepEqual(o1, o2)
}
func InterfaceToString(val interface{}) string {
return fmt.Sprintf("%v", val)
}
func InterfaceToJson(val interface{}) (string, error) {
j, err := json.Marshal(val)
if err != nil {
return "", fmt.Errorf("failed to marshal %v: %v", val, err)
}
return string(j), nil
}
func JsonStringToInterface(jsonStr string) (interface{}, error) {
var obj interface{}
err := json.Unmarshal([]byte(jsonStr), &obj)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal %s: %v", jsonStr, err)
}
return obj, nil
}
func MapToJson(m *map[string]interface{}) (string, error) {
j, err := json.Marshal(*m)
if err != nil {
return "", fmt.Errorf("failed to marshal %v: %v", *m, err)
}
return string(j), nil
}
package util
import (
"fmt"
"net/url"
)
// GetQueryParamValueFromUri takes a url and a query parameter key, and returns the value assigned to that parameter.
// This function should not be used if the value associated with the param could be an array of string values.
func GetQueryParamValueFromUri(uri, param string) (string, error) {
var value string
u, err := url.Parse(uri)
if err != nil {
return "", fmt.Errorf("failed to parse url %s: %v", uri, err)
}
m, err := url.ParseQuery(u.RawQuery)
if err != nil {
return "", fmt.Errorf("failed to parse query parameters from url %s: %v", uri, err)
}
if paramSlice, ok := m[param]; ok && len(paramSlice) > 0 {
value = paramSlice[0]
}
return value, nil
}
package util
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func WithRetries(ctx context.Context, timeout time.Duration, method func() *retry.RetryError) diag.Diagnostics {
err := diag.FromErr(retry.RetryContext(ctx, timeout, method))
if err != nil && strings.Contains(fmt.Sprintf("%v", err), "timeout while waiting for state to become") {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return WithRetries(ctx, timeout, method)
}
return err
}
func WithRetriesForRead(ctx context.Context, d *schema.ResourceData, method func() *retry.RetryError) diag.Diagnostics {
return WithRetriesForReadCustomTimeout(ctx, 5*time.Minute, d, method)
}
func WithRetriesForReadCustomTimeout(ctx context.Context, timeout time.Duration, d *schema.ResourceData, method func() *retry.RetryError) diag.Diagnostics {
err := diag.FromErr(retry.RetryContext(ctx, timeout, method))
if err != nil {
if strings.Contains(fmt.Sprintf("%v", err), "API Error: 404") {
// Set ID empty if the object isn't found after the specified timeout
d.SetId("")
}
errStringLower := strings.ToLower(fmt.Sprintf("%v", err))
if strings.Contains(errStringLower, "timeout while waiting for state to become") ||
strings.Contains(errStringLower, "context deadline exceeded") {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return WithRetriesForRead(ctx, d, method)
}
if d.Id() != "" {
consistency_checker.DeleteConsistencyCheck(d.Id())
}
}
return err
}
type checkResponseFunc func(resp *platformclientv2.APIResponse, additionalCodes ...int) bool
type callSdkFunc func() (*platformclientv2.APIResponse, diag.Diagnostics)
// Retries up to 10 times while the shouldRetry condition returns true
// Useful for adding custom retry logic to normally non-retryable error codes
func RetryWhen(shouldRetry checkResponseFunc, callSdk callSdkFunc, additionalCodes ...int) diag.Diagnostics {
var lastErr diag.Diagnostics
for i := 0; i < 10; i++ {
resp, sdkErr := callSdk()
if sdkErr != nil {
if resp != nil && shouldRetry(resp, additionalCodes...) {
// Wait a second and try again
lastErr = sdkErr
time.Sleep(time.Second)
continue
} else {
return sdkErr
}
}
// Success
return nil
}
return diag.Errorf("Exhausted retries. Last error: %v", lastErr)
}
func IsAdditionalCode(statusCode int, additionalCodes ...int) bool {
for _, additionalCode := range additionalCodes {
if statusCode == additionalCode {
return true
}
}
return false
}
func IsVersionMismatch(resp *platformclientv2.APIResponse, additionalCodes ...int) bool {
// Version mismatch from directory may be a 409 or 400 with specific error message
if resp != nil {
if resp.StatusCode == http.StatusConflict ||
resp.StatusCode == http.StatusRequestTimeout ||
IsAdditionalCode(resp.StatusCode, additionalCodes...) ||
(resp.StatusCode == http.StatusBadRequest && resp.Error != nil && strings.Contains((*resp.Error).Message, "does not match the current version")) {
return true
}
}
return false
}
func IsStatus404(resp *platformclientv2.APIResponse, additionalCodes ...int) bool {
if resp != nil {
if resp.StatusCode == http.StatusNotFound ||
resp.StatusCode == http.StatusRequestTimeout ||
resp.StatusCode == http.StatusGone ||
IsAdditionalCode(resp.StatusCode, additionalCodes...) {
return true
}
}
return false
}
func IsStatus404ByInt(respCode int, additionalCodes ...int) bool {
if respCode == http.StatusNotFound ||
respCode == http.StatusRequestTimeout ||
respCode == http.StatusGone ||
IsAdditionalCode(respCode, additionalCodes...) {
return true
}
return false
}
func IsStatus400(resp *platformclientv2.APIResponse, additionalCodes ...int) bool {
if resp != nil {
if resp.StatusCode == http.StatusBadRequest ||
resp.StatusCode == http.StatusRequestTimeout ||
IsAdditionalCode(resp.StatusCode, additionalCodes...) {
return true
}
}
return false
}
func GetBody(apiResponse *platformclientv2.APIResponse) string {
if apiResponse != nil {
return string(apiResponse.RawBody)
}
return ""
}
func IsStatus409(resp *platformclientv2.APIResponse, additionalCodes ...int) bool {
if resp != nil {
if resp.StatusCode == http.StatusConflict ||
resp.StatusCode == http.StatusRequestTimeout ||
IsAdditionalCode(resp.StatusCode, additionalCodes...) {
return true
}
}
return false
}
func IsStatus412(resp *platformclientv2.APIResponse, additionalCodes ...int) bool {
if resp != nil {
if resp.StatusCode == http.StatusPreconditionFailed ||
resp.StatusCode == http.StatusRequestTimeout ||
IsAdditionalCode(resp.StatusCode, additionalCodes...) {
return true
}
}
return false
}
func IsStatus412ByInt(respCode int, additionalCodes ...int) bool {
if respCode == http.StatusPreconditionFailed ||
respCode == http.StatusRequestTimeout ||
IsAdditionalCode(respCode, additionalCodes...) {
return true
}
return false
}
package util
import (
"regexp"
"strings"
)
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
var matchUnderscore = regexp.MustCompile("_")
func ToSnakeCase(str string) string {
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
return strings.ToLower(snake)
}
func ToCamelCase(str string) string {
terms := matchUnderscore.Split(str, -1)
camel := ""
for i, term := range terms {
if i == 0 {
camel += term
} else {
camel += strings.Title(term)
}
}
return camel
}
func StringExists(target string, slice []string) bool {
for _, str := range slice {
if str == target {
return true
}
}
return false
}
package webdeployments_configuration
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceConfigurationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
wp := getWebDeploymentConfigurationsProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
configs, resp, err := wp.getWebDeploymentsConfiguration(ctx)
if err != nil {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error retrieving web deployment configuration %s | error: %s", name, err), resp))
}
for _, config := range *configs.Entities {
if name == *config.Name {
d.SetId(*config.Id)
version := wp.determineLatestVersion(ctx, *config.Id)
if version == "draft" {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Web deployment configuration %s has no published versions and so cannot be used", name), resp))
}
_ = d.Set("version", version)
return nil
}
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No web deployment configuration was found with the name %s", name), resp))
})
}
package webdeployments_configuration
import (
"context"
"fmt"
"log"
"strconv"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *webDeploymentsConfigurationProxy
type getAllWebDeploymentsConfigurationFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy) (*platformclientv2.Webdeploymentconfigurationversionentitylisting, *platformclientv2.APIResponse, error)
type getWebdeploymentsConfigurationVersionFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy, id string, version string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error)
type determineLatestVersionFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) string
type deleteWebDeploymentConfigurationFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) (*platformclientv2.APIResponse, error)
type getWebdeploymentsConfigurationVersionsDraftFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error)
type createWebdeploymentsConfigurationFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationVersion platformclientv2.Webdeploymentconfigurationversion) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error)
type createWebdeploymentsConfigurationVersionsDraftPublishFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error)
type updateWebdeploymentsConfigurationVersionsDraftFunc func(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string, configurationVersion platformclientv2.Webdeploymentconfigurationversion) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error)
func newWebDeploymentsConfigurationProxy(clientConfig *platformclientv2.Configuration) *webDeploymentsConfigurationProxy {
webDeploymentsConfigurationApi := platformclientv2.NewWebDeploymentsApiWithConfig(clientConfig)
return &webDeploymentsConfigurationProxy{
clientConfig: clientConfig,
webDeploymentsApi: webDeploymentsConfigurationApi,
getAllWebDeploymentConfigurationsAttr: getAllWebDeploymentsConfigurationFn,
determineLatestVersionAttr: determineLatestVersionFn,
getWebdeploymentsConfigurationVersionAttr: getWebdeploymentsConfigurationVersionFn,
deleteWebDeploymentConfigurationAttr: deleteWebDeploymentConfigurationFn,
getWebdeploymentsConfigurationVersionsDraftAttr: getWebdeploymentsConfigurationVersionsDraftFn,
createWebdeploymentsConfigurationAttr: createWebdeploymentsConfigurationFn,
createWebdeploymentsConfigurationVersionsDraftPublishAttr: createWebdeploymentsConfigurationVersionsDraftPublishFn,
updateWebdeploymentsConfigurationVersionsDraftAttr: updateWebdeploymentsConfigurationVersionsDraftFn,
}
}
func getWebDeploymentConfigurationsProxy(clientConfig *platformclientv2.Configuration) *webDeploymentsConfigurationProxy {
if internalProxy == nil {
internalProxy = newWebDeploymentsConfigurationProxy(clientConfig)
}
return internalProxy
}
type webDeploymentsConfigurationProxy struct {
clientConfig *platformclientv2.Configuration
webDeploymentsApi *platformclientv2.WebDeploymentsApi
getAllWebDeploymentConfigurationsAttr getAllWebDeploymentsConfigurationFunc
getWebdeploymentsConfigurationVersionAttr getWebdeploymentsConfigurationVersionFunc
determineLatestVersionAttr determineLatestVersionFunc
deleteWebDeploymentConfigurationAttr deleteWebDeploymentConfigurationFunc
getWebdeploymentsConfigurationVersionsDraftAttr getWebdeploymentsConfigurationVersionsDraftFunc
createWebdeploymentsConfigurationAttr createWebdeploymentsConfigurationFunc
createWebdeploymentsConfigurationVersionsDraftPublishAttr createWebdeploymentsConfigurationVersionsDraftPublishFunc
updateWebdeploymentsConfigurationVersionsDraftAttr updateWebdeploymentsConfigurationVersionsDraftFunc
}
func (p *webDeploymentsConfigurationProxy) getWebDeploymentsConfiguration(ctx context.Context) (*platformclientv2.Webdeploymentconfigurationversionentitylisting, *platformclientv2.APIResponse, error) {
return p.getAllWebDeploymentConfigurationsAttr(ctx, p)
}
func (p *webDeploymentsConfigurationProxy) getWebdeploymentsConfigurationVersion(ctx context.Context, id string, version string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.getWebdeploymentsConfigurationVersionAttr(ctx, p, id, version)
}
func (p *webDeploymentsConfigurationProxy) determineLatestVersion(ctx context.Context, configurationId string) string {
return p.determineLatestVersionAttr(ctx, p, configurationId)
}
func (p *webDeploymentsConfigurationProxy) deleteWebDeploymentConfiguration(ctx context.Context, configurationId string) (*platformclientv2.APIResponse, error) {
return p.deleteWebDeploymentConfigurationAttr(ctx, p, configurationId)
}
func (p *webDeploymentsConfigurationProxy) getWebdeploymentsConfigurationVersionsDraft(ctx context.Context, configurationId string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.getWebdeploymentsConfigurationVersionsDraftAttr(ctx, p, configurationId)
}
func (p *webDeploymentsConfigurationProxy) createWebdeploymentsConfiguration(ctx context.Context, configurationVersion platformclientv2.Webdeploymentconfigurationversion) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.createWebdeploymentsConfigurationAttr(ctx, p, configurationVersion)
}
func (p *webDeploymentsConfigurationProxy) createWebdeploymentsConfigurationVersionsDraftPublish(ctx context.Context, configurationId string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.createWebdeploymentsConfigurationVersionsDraftPublishAttr(ctx, p, configurationId)
}
func (p *webDeploymentsConfigurationProxy) updateWebdeploymentsConfigurationVersionsDraft(ctx context.Context, configurationId string, configurationVersion platformclientv2.Webdeploymentconfigurationversion) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.updateWebdeploymentsConfigurationVersionsDraftAttr(ctx, p, configurationId, configurationVersion)
}
func getAllWebDeploymentsConfigurationFn(ctx context.Context, p *webDeploymentsConfigurationProxy) (*platformclientv2.Webdeploymentconfigurationversionentitylisting, *platformclientv2.APIResponse, error) {
configurations, resp, getErr := p.webDeploymentsApi.GetWebdeploymentsConfigurations(false)
if getErr != nil {
return nil, resp, fmt.Errorf("Failed to get web deployment configurations: %v", getErr)
}
return configurations, resp, nil
}
func getWebdeploymentsConfigurationVersionFn(ctx context.Context, p *webDeploymentsConfigurationProxy, id string, version string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.GetWebdeploymentsConfigurationVersion(id, version)
}
func determineLatestVersionFn(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) string {
version := ""
draft := "DRAFT"
_ = util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
versions, resp, getErr := p.webDeploymentsApi.GetWebdeploymentsConfigurationVersions(configurationId)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to determine latest version | error: %s", getErr), resp))
}
log.Printf("Failed to determine latest version. Defaulting to DRAFT. Details: %s", getErr)
version = draft
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to determine latest version | error: %s", getErr), resp))
}
maxVersion := 0
for _, v := range *versions.Entities {
if *v.Version == draft {
continue
}
APIVersion, err := strconv.Atoi(*v.Version)
if err != nil {
log.Printf("Failed to convert version %s to an integer", *v.Version)
} else {
if APIVersion > maxVersion {
maxVersion = APIVersion
}
}
}
if maxVersion == 0 {
version = draft
} else {
version = strconv.Itoa(maxVersion)
}
return nil
})
return version
}
func deleteWebDeploymentConfigurationFn(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) (*platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.DeleteWebdeploymentsConfiguration(configurationId)
}
func getWebdeploymentsConfigurationVersionsDraftFn(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.GetWebdeploymentsConfigurationVersionsDraft(configurationId)
}
func createWebdeploymentsConfigurationFn(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationVersion platformclientv2.Webdeploymentconfigurationversion) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.PostWebdeploymentsConfigurations(configurationVersion)
}
func createWebdeploymentsConfigurationVersionsDraftPublishFn(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.PostWebdeploymentsConfigurationVersionsDraftPublish(configurationId)
}
func updateWebdeploymentsConfigurationVersionsDraftFn(ctx context.Context, p *webDeploymentsConfigurationProxy, configurationId string, configurationVersion platformclientv2.Webdeploymentconfigurationversion) (*platformclientv2.Webdeploymentconfigurationversion, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.PutWebdeploymentsConfigurationVersionsDraft(configurationId, configurationVersion)
}
package webdeployments_configuration
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
wdcUtils "terraform-provider-genesyscloud/genesyscloud/webdeployments_configuration/utils"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllWebDeploymentConfigurations(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
wp := getWebDeploymentConfigurationsProxy(clientConfig)
configurations, resp, err := wp.getWebDeploymentsConfiguration(ctx)
if err != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get webdeployments configuration error: %s", err), resp)
}
for _, configuration := range *configurations.Entities {
resources[*configuration.Id] = &resourceExporter.ResourceMeta{Name: *configuration.Name}
}
return resources, nil
}
func waitForConfigurationDraftToBeActive(ctx context.Context, meta interface{}, id string) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wp := getWebDeploymentConfigurationsProxy(sdkConfig)
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
configuration, resp, err := wp.getWebdeploymentsConfigurationVersionsDraft(ctx, id)
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error verifying active status for new web deployment configuration %s | error: %s", id, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error verifying active status for new web deployment configuration %s | error: %s", id, err), resp))
}
if *configuration.Status == "Active" {
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("web deployment configuration %s not active yet. Status: %s", id, *configuration.Status), resp))
})
}
func createWebDeploymentConfiguration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wp := getWebDeploymentConfigurationsProxy(sdkConfig)
name, inputCfg := wdcUtils.BuildWebDeploymentConfigurationFromResourceData(d)
log.Printf("Creating web deployment configuration %s", name)
log.Println("Current Deployment: ", inputCfg)
diagErr := util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
configuration, resp, err := wp.createWebdeploymentsConfiguration(ctx, *inputCfg)
if err != nil {
var extraErrorInfo string
featureIsNotImplemented, fieldName := wdcUtils.FeatureNotImplemented(resp)
if featureIsNotImplemented {
extraErrorInfo = fmt.Sprintf("Feature '%s' is not yet implemented", fieldName)
}
if util.IsStatus400(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to create web deployment configuration %s: %s. %s", name, err, extraErrorInfo), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to create web deployment configuration %s: %s. %s", name, err, extraErrorInfo), resp))
}
d.SetId(*configuration.Id)
_ = d.Set("status", configuration.Status)
return nil
})
if diagErr != nil {
return diagErr
}
activeError := waitForConfigurationDraftToBeActive(ctx, meta, d.Id())
if activeError != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Web deployment configuration %s did not become active and could not be published", name), fmt.Errorf("%v", activeError))
}
diagErr = util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
configuration, resp, err := wp.createWebdeploymentsConfigurationVersionsDraftPublish(ctx, d.Id())
if err != nil {
if util.IsStatus400(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error publishing web deployment configuration %s | error: %s", name, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error publishing web deployment configuration %s | error: %s", name, err), resp))
}
_ = d.Set("version", configuration.Version)
_ = d.Set("status", configuration.Status)
log.Printf("Created web deployment configuration %s %s", name, *configuration.Id)
return nil
})
if diagErr != nil {
return diagErr
}
return readWebDeploymentConfiguration(ctx, d, meta)
}
func readWebDeploymentConfiguration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wp := getWebDeploymentConfigurationsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceWebDeploymentConfiguration(), constants.DefaultConsistencyChecks, resourceName)
version := d.Get("version").(string)
log.Printf("Reading web deployment configuration %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
if version == "" {
version = wp.determineLatestVersion(ctx, d.Id())
}
configuration, resp, getErr := wp.getWebdeploymentsConfigurationVersion(ctx, d.Id(), version)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read web deployment configuration %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read web deployment configuration %s | error: %s", d.Id(), getErr), resp))
}
_ = d.Set("name", *configuration.Name)
resourcedata.SetNillableValue(d, "description", configuration.Description)
resourcedata.SetNillableValue(d, "languages", configuration.Languages)
resourcedata.SetNillableValue(d, "default_language", configuration.DefaultLanguage)
resourcedata.SetNillableValue(d, "status", configuration.Status)
resourcedata.SetNillableValue(d, "version", configuration.Version)
if configuration.HeadlessMode != nil {
resourcedata.SetNillableValue(d, "headless_mode_enabled", configuration.HeadlessMode.Enabled)
}
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "custom_i18n_labels", configuration.CustomI18nLabels, wdcUtils.FlattenCustomI18nLabels)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "position", configuration.Position, wdcUtils.FlattenPosition)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "authentication_settings", configuration.AuthenticationSettings, wdcUtils.FlattenAuthenticationSettings)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "messenger", configuration.Messenger, wdcUtils.FlattenMessengerSettings)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "cobrowse", configuration.Cobrowse, wdcUtils.FlattenCobrowseSettings)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "journey_events", configuration.JourneyEvents, wdcUtils.FlattenJourneyEvents)
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "support_center", configuration.SupportCenter, wdcUtils.FlattenSupportCenterSettings)
log.Printf("Read web deployment configuration %s %s", d.Id(), *configuration.Name)
return cc.CheckState(d)
})
}
func updateWebDeploymentConfiguration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wp := getWebDeploymentConfigurationsProxy(sdkConfig)
name, inputCfg := wdcUtils.BuildWebDeploymentConfigurationFromResourceData(d)
log.Printf("Updating web deployment configuration %s", name)
diagErr := util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := wp.updateWebdeploymentsConfigurationVersionsDraft(ctx, d.Id(), *inputCfg)
if err != nil {
if util.IsStatus400(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error updating web deployment configuration %s | error: %s", name, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error updating web deployment configuration %s | error: %s", name, err), resp))
}
return nil
})
if diagErr != nil {
return diagErr
}
activeError := waitForConfigurationDraftToBeActive(ctx, meta, d.Id())
if activeError != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Web deployment configuration %s did not become active and could not be published", name), fmt.Errorf("%v", activeError))
}
diagErr = util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
configuration, resp, err := wp.createWebdeploymentsConfigurationVersionsDraftPublish(ctx, d.Id())
if err != nil {
if util.IsStatus400(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error publishing web deployment configuration %s | error: %s", name, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error publishing web deployment configuration %s | error: %s", name, err), resp))
}
_ = d.Set("version", configuration.Version)
_ = d.Set("status", configuration.Status)
return nil
})
if diagErr != nil {
return diagErr
}
log.Printf("Finished updating web deployment configuration %s", name)
return readWebDeploymentConfiguration(ctx, d, meta)
}
func deleteWebDeploymentConfiguration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wp := getWebDeploymentConfigurationsProxy(sdkConfig)
log.Printf("Deleting web deployment configuration %s", name)
resp, err := wp.deleteWebDeploymentConfiguration(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete web deployment configuration %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := wp.getWebdeploymentsConfigurationVersionsDraft(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted web deployment configuration %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting web deployment configuration %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Web deployment configuration %s still exists", d.Id()), resp))
})
}
package webdeployments_configuration
import (
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
gcloud "terraform-provider-genesyscloud/genesyscloud/validators"
wdcUtils "terraform-provider-genesyscloud/genesyscloud/webdeployments_configuration/utils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
const resourceName = "genesyscloud_webdeployments_configuration"
// SetRegistrar registers all the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceWebDeploymentsConfiguration())
l.RegisterResource(resourceName, ResourceWebDeploymentConfiguration())
l.RegisterExporter(resourceName, WebDeploymentConfigurationExporter())
}
var (
customI18nLabel = &schema.Resource{
Schema: map[string]*schema.Schema{
"language": {
Description: "Language of localized labels in homescreen app (eg. en-us, de-de)",
Type: schema.TypeString,
Optional: true,
},
"localized_labels": {
Description: "Contains localized labels used in homescreen app",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Description: "Contains localized label key used in messenger homescreen",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"MessengerHomeHeaderTitle", "MessengerHomeHeaderSubTitle"}, false),
},
"value": {
Description: "Contains localized label value used in messenger homescreen",
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
position = &schema.Resource{
Schema: map[string]*schema.Schema{
"alignment": {
Description: "The alignment for position",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Auto", "Left", "Right"}, false),
},
"side_space": {
Description: "The sidespace value for position",
Type: schema.TypeInt,
Optional: true,
},
"bottom_space": {
Description: "The bottomspace value for position",
Type: schema.TypeInt,
Optional: true,
},
},
}
messengerStyle = &schema.Resource{
Schema: map[string]*schema.Schema{
"primary_color": {
Description: "The primary color of messenger in hexadecimal",
Type: schema.TypeString,
Optional: true,
},
},
}
launcherButtonSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"visibility": {
Description: "The visibility settings for the button.Valid values: On, Off, OnDemand",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"On",
"Off",
"OnDemand",
}, false),
},
},
}
homeScreen = &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "Whether or not home screen is enabled",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"logo_url": {
Description: "URL for custom logo to appear in home screen",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
fileUploadMode = &schema.Resource{
Schema: map[string]*schema.Schema{
"file_types": {
Description: "A list of supported content types for uploading files.Valid values: image/jpeg, image/gif, image/png",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"max_file_size_kb": {
Description: "The maximum file size for file uploads in kilobytes. Default is 10240 (10 MB)",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 10240),
},
},
}
fileUploadSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"mode": {
Description: "The list of supported file upload modes",
Type: schema.TypeList,
Optional: true,
Elem: fileUploadMode,
},
},
}
messengerSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "Whether or not messenger is enabled",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"styles": {
Description: "The style settings for messenger",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: messengerStyle,
},
"launcher_button": {
Description: "The settings for the launcher button",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: launcherButtonSettings,
},
"home_screen": {
Description: "The settings for the home screen",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: homeScreen,
},
"file_upload": {
Description: "File upload settings for messenger",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: fileUploadSettings,
},
"apps": {
Description: "The apps embedded in the messenger",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"conversations": {
Description: "Conversation settings that handles chats within the messenger",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "The toggle to enable or disable conversations",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"show_agent_typing_indicator": {
Description: "The toggle to enable or disable typing indicator for messenger",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"show_user_typing_indicator": {
Description: "The toggle to enable or disable typing indicator for messenger",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"auto_start_enabled": {
Description: "The auto start for the messenger conversation",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"markdown_enabled": {
Description: "The markdown for the messenger app",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"conversation_disconnect": {
Description: "The conversation disconnect for the messenger app",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "whether or not conversation disconnect setting is enabled",
Type: schema.TypeBool,
Optional: true,
},
"type": {
Description: "Conversation disconnect type",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Send", "ReadOnly"}, false),
},
},
},
},
"conversation_clear_enabled": {
Description: "The conversation clear settings for the messenger app",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"humanize": {
Description: "The humanize conversations settings for the messenger app",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "Whether or not humanize conversations setting is enabled",
Type: schema.TypeBool,
Optional: true,
},
"bot": {
Description: "Bot messenger profile setting",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the bot",
Type: schema.TypeString,
Optional: true,
},
"avatar_url": {
Description: "The avatar URL of the bot",
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
},
},
},
},
},
"knowledge": {
Description: "The knowledge base config for messenger",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "whether or not knowledge base is enabled",
Type: schema.TypeBool,
Optional: true,
},
"knowledge_base_id": {
Description: "The knowledge base for messenger",
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
},
},
},
}
cobrowseSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "Whether or not cobrowse is enabled",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"allow_agent_control": {
Description: "Whether agent can take control over customer's screen or not",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"channels": {
Description: "List of channels through which cobrowse is available (for now only Webmessaging and Voice)",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"Webmessaging", "Voice"}, false),
},
},
"mask_selectors": {
Description: "List of CSS selectors which should be masked when screen sharing is active",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"readonly_selectors": {
Description: "List of CSS selectors which should be read-only when screen sharing is active",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
selectorEventTrigger = &schema.Resource{
Schema: map[string]*schema.Schema{
"selector": {
Description: "Element that triggers event",
Type: schema.TypeString,
Required: true,
},
"event_name": {
Description: "Name of event triggered when element matching selector is interacted with",
Type: schema.TypeString,
Required: true,
},
},
}
formsTrackTrigger = &schema.Resource{
Schema: map[string]*schema.Schema{
"selector": {
Description: "Form element that triggers the form submitted or abandoned event",
Type: schema.TypeString,
Required: true,
},
"form_name": {
Description: "Prefix for the form submitted or abandoned event name",
Type: schema.TypeString,
Required: true,
},
"capture_data_on_form_abandon": {
Description: "Whether to capture the form data in the form abandoned event",
Type: schema.TypeBool,
Required: true,
},
"capture_data_on_form_submit": {
Description: "Whether to capture the form data in the form submitted event",
Type: schema.TypeBool,
Required: true,
},
},
}
idleEventTrigger = &schema.Resource{
Schema: map[string]*schema.Schema{
"event_name": {
Description: "Name of event triggered after period of inactivity",
Type: schema.TypeString,
Required: true,
},
"idle_after_seconds": {
Description: "Number of seconds of inactivity before an event is triggered",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(30),
},
},
}
scrollPercentageEventTrigger = &schema.Resource{
Schema: map[string]*schema.Schema{
"event_name": {
Description: "Name of event triggered after scrolling to the specified percentage",
Type: schema.TypeString,
Required: true,
},
"percentage": {
Description: "Percentage of a webpage at which an event is triggered",
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(0, 100),
},
},
}
customMessage = &schema.Resource{
Schema: map[string]*schema.Schema{
"default_value": {
Description: "Default value for the custom message",
Type: schema.TypeString,
Required: true,
},
"type": {
Description: "The custom message type. (Welcome or Fallback)",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Welcome", "Fallback"}, false),
},
},
}
supportCenterModuleSetting = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "Screen module type",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Search", "Categories", "FAQ", "Contact", "Results", "Article", "TopViewedArticles"}, false),
},
"enabled": {
Description: "Whether or not knowledge portal (previously support center) screen module is enabled",
Type: schema.TypeBool,
Required: true,
},
"compact_category_module_template_active": {
Description: "Whether the Support Center Compact Category Module Template is active or not",
Type: schema.TypeBool,
Optional: true,
},
"detailed_category_module_template": {
Description: "Detailed category module template settings",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"active": {
Description: "Whether the Support Center Detailed Category Module Template is active or not",
Type: schema.TypeBool,
Required: true,
},
"sidebar_enabled": {
Description: "Whether the Support Center Detailed Category Module Sidebar is active or not",
Type: schema.TypeBool,
Required: true,
},
},
},
},
},
}
supportCenterScreen = &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Description: "The type of the screen",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Home", "Category", "SearchResults", "Article"}, false),
},
"module_settings": {
Description: "Module settings for the screen, valid modules for each screenType: Home: Search, Categories, TopViewedArticles; Category: Search, Categories; SearchResults: Search, Results; Article: Search, Article;",
Type: schema.TypeList,
Required: true,
Elem: supportCenterModuleSetting,
},
},
}
styleSetting = &schema.Resource{
Schema: map[string]*schema.Schema{
"hero_style_setting": {
Description: "Knowledge portal (previously support center) hero customizations",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"background_color": {
Description: "Background color for hero section, in hexadecimal format, eg #ffffff",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateHexColor,
},
"text_color": {
Description: "Text color for hero section, in hexadecimal format, eg #ffffff",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateHexColor,
},
"image_uri": {
Description: "Background image for hero section",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.IsURLWithHTTPS,
},
},
},
},
"global_style_setting": {
Description: "Knowledge portal (previously support center) global customizations",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"background_color": {
Description: "Global background color, in hexadecimal format, eg #ffffff",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateHexColor,
},
"primary_color": {
Description: "Global primary color, in hexadecimal format, eg #ffffff",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateHexColor,
},
"primary_color_dark": {
Description: "Global dark primary color, in hexadecimal format, eg #ffffff",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateHexColor,
},
"primary_color_light": {
Description: "Global light primary color, in hexadecimal format, eg #ffffff",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateHexColor,
},
"text_color": {
Description: "Global text color, in hexadecimal format, eg #ffffff",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: gcloud.ValidateHexColor,
},
"font_family": {
Description: "Global font family",
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
journeyEventsSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "Whether or not journey event collection is enabled",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"excluded_query_parameters": {
Description: "List of parameters to be excluded from the query string",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"should_keep_url_fragment": {
Description: "Whether or not to keep the URL fragment",
Type: schema.TypeBool,
Optional: true,
},
"search_query_parameters": {
Description: "List of query parameters used for search (e.g. 'q')",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"pageview_config": {
Description: "Controls how the pageview events are tracked.Valid values: Auto, Once, Off",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"Auto",
"Once",
"Off",
}, false),
},
"click_event": {
Description: "Details about a selector event trigger",
Type: schema.TypeList,
Optional: true,
Elem: selectorEventTrigger,
},
"form_track_event": {
Description: "Details about a forms tracking event trigger",
Type: schema.TypeList,
Optional: true,
Elem: formsTrackTrigger,
},
"idle_event": {
Description: "Details about an idle event trigger",
Type: schema.TypeList,
Optional: true,
Elem: idleEventTrigger,
},
"in_viewport_event": {
Description: "Details about a selector event trigger",
Type: schema.TypeList,
Optional: true,
Elem: selectorEventTrigger,
},
"scroll_depth_event": {
Description: "Details about a scroll percentage event trigger",
Type: schema.TypeList,
Optional: true,
Elem: scrollPercentageEventTrigger,
},
},
}
supportCenterSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "Whether or not knowledge portal (previously support center) is enabled",
Type: schema.TypeBool,
Required: true,
},
"knowledge_base_id": {
Description: "The knowledge base for knowledge portal (previously support center)",
Type: schema.TypeString,
Optional: true,
},
"custom_messages": {
Description: "Customizable display texts for knowledge portal",
Type: schema.TypeList,
Optional: true,
Elem: customMessage,
},
"router_type": {
Description: "Router type for knowledge portal",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Hash", "Browser"}, false),
},
"screens": {
Description: "Available screens for the knowledge portal with its modules",
Type: schema.TypeList,
Optional: true,
Elem: supportCenterScreen,
},
"enabled_categories": {
Description: "Featured categories for knowledge portal (previously support center) home screen",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"category_id": {
Description: "The knowledge base category id",
Type: schema.TypeString,
Required: true,
},
"image_uri": {
Description: "Source URL for the featured category",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsURLWithHTTPS,
},
},
},
},
"style_setting": {
Description: "Style attributes for knowledge portal (previously support center)",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: styleSetting,
},
"feedback_enabled": {
Description: "Whether or not requesting customer feedback on article content and article search results is enabled",
Type: schema.TypeBool,
Optional: true,
},
},
}
authenticationSettings = &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Description: "Indicate if these auth is required for this deployment. If, for example, this flag is set to true then webmessaging sessions can not send messages unless the end-user is authenticated.",
Type: schema.TypeBool,
Required: true,
},
"integration_id": {
Description: "The integration identifier which contains the auth settings required on the deployment.",
Type: schema.TypeString,
Required: true,
},
},
}
)
func DataSourceWebDeploymentsConfiguration() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Web Deployments Configurations. Select a configuration by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceConfigurationRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the configuration",
Type: schema.TypeString,
Required: true,
},
"version": {
Description: "The version of the configuration.",
Type: schema.TypeString,
Computed: true,
},
},
}
}
func ResourceWebDeploymentConfiguration() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Web Deployment Configuration",
CreateContext: provider.CreateWithPooledClient(createWebDeploymentConfiguration),
ReadContext: provider.ReadWithPooledClient(readWebDeploymentConfiguration),
UpdateContext: provider.UpdateWithPooledClient(updateWebDeploymentConfiguration),
DeleteContext: provider.DeleteWithPooledClient(deleteWebDeploymentConfiguration),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Deployment name",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 100),
},
"description": {
Description: "Deployment description",
Type: schema.TypeString,
Optional: true,
},
"headless_mode_enabled": {
Description: "Headless Mode Support which Controls UI components. When enabled, native UI components will be disabled and allows for custom-built UI.",
Type: schema.TypeBool,
Optional: true,
},
"languages": {
Description: "A list of languages supported on the configuration.",
Type: schema.TypeList,
Required: true,
MinItems: 1,
Elem: &schema.Schema{Type: schema.TypeString},
},
"default_language": {
Description: "The default language to use for the configuration.",
Type: schema.TypeString,
Required: true,
},
"status": {
Description: "The current status of the deployment. Valid values: Pending, Active, Inactive, Error, Deleting.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"Pending",
"Active",
"Inactive",
"Error",
"Deleting",
}, false),
DiffSuppressFunc: wdcUtils.ValidateConfigurationStatusChange,
},
"version": {
Description: "The version of the configuration.",
Type: schema.TypeString,
Computed: true,
MaxItems: 0,
},
"custom_i18n_labels": {
Description: "The localization settings for homescreen app",
Type: schema.TypeList,
Optional: true,
Elem: customI18nLabel,
},
"position": {
Description: "Settings concerning position",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: position,
},
"messenger": {
Description: "Settings concerning messenger",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: messengerSettings,
},
"cobrowse": {
Description: "Settings concerning cobrowse",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: cobrowseSettings,
},
"journey_events": {
Description: "Settings concerning journey events",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: journeyEventsSettings,
},
"support_center": {
Description: "Settings concerning knowledge portal (previously support center)",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: supportCenterSettings,
},
"authentication_settings": {
Description: "Settings for authenticated webdeployments.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: authenticationSettings,
},
},
CustomizeDiff: wdcUtils.CustomizeConfigurationDiff,
}
}
func WebDeploymentConfigurationExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllWebDeploymentConfigurations),
ExcludedAttributes: []string{"version"},
RemoveIfMissing: map[string][]string{
"authentication_settings": {"integration_id"},
},
}
}
package webdeployments_deployment
import (
"context"
"fmt"
"net/http"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceDeploymentRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
sdkConfig := m.(*provider.ProviderMeta).ClientConfig
wd := getWebDeploymentsProxy(sdkConfig)
name := d.Get("name").(string)
return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError {
deployments, resp, err := wd.getWebDeployments(ctx)
if err != nil && resp.StatusCode == http.StatusNotFound {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No web deployment record found %s | error: %s", name, err), resp))
}
if err != nil && resp.StatusCode != http.StatusNotFound {
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error retrieving web deployment %s | error: %s", name, err), resp))
}
for _, deployment := range *deployments.Entities {
if name == *deployment.Name {
d.SetId(*deployment.Id)
return nil
}
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No web deployment was found with the name %s", name), resp))
})
}
package webdeployments_deployment
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"log"
"strconv"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
var internalProxy *webDeploymentsProxy
type getAllWebDeploymentsFunc func(ctx context.Context, p *webDeploymentsProxy) (*platformclientv2.Expandablewebdeploymententitylisting, *platformclientv2.APIResponse, error)
type getWebDeploymentsFunc func(ctx context.Context, p *webDeploymentsProxy, deployId string) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error)
type createWebdeploymentsFunc func(ctx context.Context, p *webDeploymentsProxy, deployment platformclientv2.Webdeployment) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error)
type updateWebdeploymentsFunc func(ctx context.Context, p *webDeploymentsProxy, deploymentId string, deployment platformclientv2.Webdeployment) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error)
type deleteWebdeploymentsFunc func(ctx context.Context, p *webDeploymentsProxy, deploymentId string) (*platformclientv2.APIResponse, error)
type determineLatestVersionFunc func(ctx context.Context, p *webDeploymentsProxy, configurationId string) (string, []string, diag.Diagnostics)
type webDeploymentsProxy struct {
clientConfig *platformclientv2.Configuration
webDeploymentsApi *platformclientv2.WebDeploymentsApi
getAllWebDeploymentsAttr getAllWebDeploymentsFunc
getWebDeploymentAttr getWebDeploymentsFunc
createWebDeploymentAttr createWebdeploymentsFunc
updateWebDeploymentAttr updateWebdeploymentsFunc
deleteWebDeploymentAttr deleteWebdeploymentsFunc
determineLatestVersionAttr determineLatestVersionFunc
}
func newWebDeploymentsProxy(clientConfig *platformclientv2.Configuration) *webDeploymentsProxy {
webDeploymentsApi := platformclientv2.NewWebDeploymentsApiWithConfig(clientConfig)
return &webDeploymentsProxy{
clientConfig: clientConfig,
webDeploymentsApi: webDeploymentsApi,
getAllWebDeploymentsAttr: getAllWebDeploymentsFn,
getWebDeploymentAttr: getWebDeploymentsFn,
createWebDeploymentAttr: createWebdeploymentsFn,
updateWebDeploymentAttr: updateWebdeploymentsFn,
deleteWebDeploymentAttr: deleteWebdeploymentsFn,
determineLatestVersionAttr: determineLatestVersionFn,
}
}
func getWebDeploymentsProxy(clientConfig *platformclientv2.Configuration) *webDeploymentsProxy {
if internalProxy == nil {
internalProxy = newWebDeploymentsProxy(clientConfig)
}
return internalProxy
}
func (p *webDeploymentsProxy) getWebDeployments(ctx context.Context) (*platformclientv2.Expandablewebdeploymententitylisting, *platformclientv2.APIResponse, error) {
return p.getAllWebDeploymentsAttr(ctx, p)
}
func (p *webDeploymentsProxy) getWebDeployment(ctx context.Context, deployId string) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error) {
return p.getWebDeploymentAttr(ctx, p, deployId)
}
func (p *webDeploymentsProxy) determineLatestVersion(ctx context.Context, configurationId string) (string, []string, diag.Diagnostics) {
return p.determineLatestVersionAttr(ctx, p, configurationId)
}
func (p *webDeploymentsProxy) createWebDeployment(ctx context.Context, deployment platformclientv2.Webdeployment) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error) {
return p.createWebDeploymentAttr(ctx, p, deployment)
}
func (p *webDeploymentsProxy) updateWebDeployment(ctx context.Context, deploymentId string, deployment platformclientv2.Webdeployment) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error) {
return p.updateWebDeploymentAttr(ctx, p, deploymentId, deployment)
}
func (p *webDeploymentsProxy) deleteWebDeployment(ctx context.Context, deploymentId string) (*platformclientv2.APIResponse, error) {
return p.deleteWebDeploymentAttr(ctx, p, deploymentId)
}
func getAllWebDeploymentsFn(ctx context.Context, p *webDeploymentsProxy) (*platformclientv2.Expandablewebdeploymententitylisting, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.GetWebdeploymentsDeployments([]string{})
}
func getWebDeploymentsFn(ctx context.Context, p *webDeploymentsProxy, deployId string) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.GetWebdeploymentsDeployment(deployId, []string{})
}
func createWebdeploymentsFn(ctx context.Context, p *webDeploymentsProxy, deployment platformclientv2.Webdeployment) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.PostWebdeploymentsDeployments(deployment)
}
func updateWebdeploymentsFn(ctx context.Context, p *webDeploymentsProxy, deploymentId string, deployment platformclientv2.Webdeployment) (*platformclientv2.Webdeployment, *platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.PutWebdeploymentsDeployment(deploymentId, deployment)
}
func deleteWebdeploymentsFn(ctx context.Context, p *webDeploymentsProxy, deploymentId string) (*platformclientv2.APIResponse, error) {
return p.webDeploymentsApi.DeleteWebdeploymentsDeployment(deploymentId)
}
func determineLatestVersionFn(ctx context.Context, p *webDeploymentsProxy, configurationId string) (string, []string, diag.Diagnostics) {
version := ""
draft := "DRAFT"
versionList := []string{}
err := util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
versions, resp, getErr := p.webDeploymentsApi.GetWebdeploymentsConfigurationVersions(configurationId)
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(fmt.Errorf("Failed to determine latest version %s", getErr))
}
log.Printf("Failed to determine latest version. Defaulting to DRAFT. Details: %s", getErr)
version = draft
return retry.NonRetryableError(fmt.Errorf("Failed to determine latest version %s", getErr))
}
maxVersion := 0
for _, v := range *versions.Entities {
if *v.Version == draft {
versionList = append(versionList, *v.Version)
continue
}
APIVersion, err := strconv.Atoi(*v.Version)
if err != nil {
log.Printf("Failed to convert version %s to an integer", *v.Version)
} else {
versionList = append(versionList, *v.Version)
if APIVersion > maxVersion {
maxVersion = APIVersion
}
}
}
if maxVersion == 0 {
version = draft
} else {
version = strconv.Itoa(maxVersion)
}
return nil
})
if err != nil {
return "", nil, err
}
return version, versionList, nil
}
package webdeployments_deployment
import (
"context"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/constants"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"terraform-provider-genesyscloud/genesyscloud/consistency_checker"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
lists "terraform-provider-genesyscloud/genesyscloud/util/lists"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func getAllWebDeployments(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) {
resources := make(resourceExporter.ResourceIDMetaMap)
wd := getWebDeploymentsProxy(clientConfig)
deployments, resp, getErr := wd.getWebDeployments(ctx)
if getErr != nil {
return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get web deployments error: %s", getErr), resp)
}
for _, deployment := range *deployments.Entities {
resources[*deployment.Id] = &resourceExporter.ResourceMeta{Name: *deployment.Name}
}
return resources, nil
}
func createWebDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
allowAllDomains := d.Get("allow_all_domains").(bool)
allowedDomains := lists.InterfaceListToStrings(d.Get("allowed_domains").([]interface{}))
err := validAllowedDomainsSettings(d)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to create web deployment %s", name), err)
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wd := getWebDeploymentsProxy(sdkConfig)
log.Printf("Creating web deployment %s", name)
configId := d.Get("configuration.0.id").(string)
inputConfigVersion := d.Get("configuration.0.version").(string)
flow := util.BuildSdkDomainEntityRef(d, "flow_id")
configVersion, versionList, er := wd.determineLatestVersion(ctx, configId)
if er != nil {
return er
}
if inputConfigVersion == "" {
inputConfigVersion = configVersion
}
exists := util.StringExists(inputConfigVersion, versionList)
if !exists {
log.Printf("For Web deployment Resource %v, Configuration Version Input %v does not match with any existing versions %v",
name, inputConfigVersion, versionList)
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("For Web Deployment Resource %v, Configuration Version Input %v does not match with any existing versions %v", name, inputConfigVersion, versionList), nil)
}
inputDeployment := platformclientv2.Webdeployment{
Name: &name,
Configuration: &platformclientv2.Webdeploymentconfigurationversionentityref{
Id: &configId,
Version: &inputConfigVersion,
},
AllowAllDomains: &allowAllDomains,
AllowedDomains: &allowedDomains,
}
if description != "" {
inputDeployment.Description = &description
}
if flow != nil {
inputDeployment.Flow = flow
}
diagErr := util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
deployment, resp, err := wd.createWebDeployment(ctx, inputDeployment)
if err != nil {
if util.IsStatus400(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to create web deployment %s | error: %s", name, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to create web deployment %s | error: %s", name, err), resp))
}
d.SetId(*deployment.Id)
log.Printf("Created web deployment %s %s %s", name, *deployment.Id, resp.CorrelationID)
return nil
})
if diagErr != nil {
return diagErr
}
time.Sleep(10 * time.Second)
activeError := waitForDeploymentToBeActive(ctx, sdkConfig, d.Id())
if activeError != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Web deployment %s did not become active and could not be created", name), fmt.Errorf("%v", activeError))
}
return readWebDeployment(ctx, d, meta)
}
func waitForDeploymentToBeActive(ctx context.Context, sdkConfig *platformclientv2.Configuration, id string) diag.Diagnostics {
wd := getWebDeploymentsProxy(sdkConfig)
return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError {
deployment, resp, err := wd.getWebDeployment(ctx, id)
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error verifying active status for new web deployment %s | error: %s", id, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error verifying active status for new web deployment %s | error: %s", id, err), resp))
}
if *deployment.Status == "Active" {
return nil
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Web deployment %s not active yet | Status: %s", id, *deployment.Status), resp))
})
}
func readWebDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wd := getWebDeploymentsProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceWebDeployment(), constants.DefaultConsistencyChecks, resourceName)
log.Printf("Reading web deployment %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
deployment, resp, getErr := wd.getWebDeployment(ctx, d.Id())
if getErr != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read web deployment %s | error: %s", d.Id(), getErr), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read web deployment %s | error: %s", d.Id(), getErr), resp))
}
_ = d.Set("name", *deployment.Name)
if deployment.Description != nil {
_ = d.Set("description", *deployment.Description)
}
if deployment.AllowAllDomains != nil {
_ = d.Set("allow_all_domains", *deployment.AllowAllDomains)
}
_ = d.Set("configuration", flattenConfiguration(deployment.Configuration))
if deployment.AllowedDomains != nil && len(*deployment.AllowedDomains) > 0 {
_ = d.Set("allowed_domains", *deployment.AllowedDomains)
}
if deployment.Flow != nil {
_ = d.Set("flow_id", *deployment.Flow.Id)
}
if deployment.Status != nil {
_ = d.Set("status", *deployment.Status)
}
log.Printf("Read web deployment %s %s", d.Id(), *deployment.Name)
return cc.CheckState(d)
})
}
func updateWebDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
description := d.Get("description").(string)
allowAllDomains := d.Get("allow_all_domains").(bool)
allowedDomains := lists.InterfaceListToStrings(d.Get("allowed_domains").([]interface{}))
status := d.Get("status").(string)
err := validAllowedDomainsSettings(d)
if err != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Failed to update web deployment %s", name), err)
}
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wd := getWebDeploymentsProxy(sdkConfig)
log.Printf("Updating web deployment %s", name)
configId := d.Get("configuration.0.id").(string)
flow := util.BuildSdkDomainEntityRef(d, "flow_id")
// always update to latest version of configuration during update of an existing webdeployment
configVersion, _, er := wd.determineLatestVersion(ctx, configId)
if err != nil {
return er
}
inputDeployment := platformclientv2.Webdeployment{
Name: &name,
Configuration: &platformclientv2.Webdeploymentconfigurationversionentityref{
Id: &configId,
Version: &configVersion,
},
AllowAllDomains: &allowAllDomains,
AllowedDomains: &allowedDomains,
Status: &status,
}
if description != "" {
inputDeployment.Description = &description
}
if flow != nil {
inputDeployment.Flow = flow
}
diagErr := util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := wd.updateWebDeployment(ctx, d.Id(), inputDeployment)
if err != nil {
if util.IsStatus400(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error updating web deployment %s | error: %s", name, err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error updating web deployment %s | error: %s", name, err), resp))
}
return nil
})
if diagErr != nil {
return diagErr
}
activeError := waitForDeploymentToBeActive(ctx, sdkConfig, d.Id())
if activeError != nil {
return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Web deployment %s did not become active and could not be created", name), fmt.Errorf("%v", activeError))
}
log.Printf("Finished updating web deployment %s", name)
return readWebDeployment(ctx, d, meta)
}
func deleteWebDeployment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
name := d.Get("name").(string)
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
wd := getWebDeploymentsProxy(sdkConfig)
log.Printf("Deleting web deployment %s", name)
resp, err := wd.deleteWebDeployment(ctx, d.Id())
if err != nil {
return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete web deployment %s error: %s", name, err), resp)
}
return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError {
_, resp, err := wd.getWebDeployment(ctx, d.Id())
if err != nil {
if util.IsStatus404(resp) {
log.Printf("Deleted web deployment %s", d.Id())
return nil
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting web deployment %s | error: %s", d.Id(), err), resp))
}
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Web deployment %s still exists", d.Id()), resp))
})
}
package webdeployments_deployment
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
const resourceName = "genesyscloud_webdeployments_deployment"
// SetRegistrar registers all the resources, datasources and exporters in the package
func SetRegistrar(l registrar.Registrar) {
l.RegisterDataSource(resourceName, DataSourceWebDeploymentsDeployment())
l.RegisterResource(resourceName, ResourceWebDeployment())
l.RegisterExporter(resourceName, WebDeploymentExporter())
}
func ResourceWebDeployment() *schema.Resource {
return &schema.Resource{
Description: "Genesys Cloud Web Deployment",
CreateContext: provider.CreateWithPooledClient(createWebDeployment),
ReadContext: provider.ReadWithPooledClient(readWebDeployment),
UpdateContext: provider.UpdateWithPooledClient(updateWebDeployment),
DeleteContext: provider.DeleteWithPooledClient(deleteWebDeployment),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{
"name": {
Description: "Deployment name",
Type: schema.TypeString,
Required: true,
},
"description": {
Description: "Deployment description",
Type: schema.TypeString,
Optional: true,
},
"allow_all_domains": {
Description: "Whether all domains are allowed or not. allowedDomains must be empty when this is true.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"allowed_domains": {
Description: "The list of domains that are approved to use this deployment; the list will be added to CORS headers for ease of web use.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"flow_id": {
Description: "A reference to the inboundshortmessage flow used by this deployment.",
Type: schema.TypeString,
Optional: true,
},
"status": {
Description: "The current status of the deployment. Valid values: Pending, Active, Inactive, Error, Deleting.",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{
"Pending",
"Active",
"Inactive",
"Error",
"Deleting",
}, false),
DiffSuppressFunc: validateDeploymentStatusChange,
},
"configuration": {
Description: "The published configuration version used by this deployment",
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Required: true,
},
"version": {
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: alwaysDifferent, // The newly-computed configuration version is not available when computing the diff so we assume it will be different
},
},
},
},
},
}
}
func WebDeploymentExporter() *resourceExporter.ResourceExporter {
return &resourceExporter.ResourceExporter{
GetResourcesFunc: provider.GetAllWithPooledClient(getAllWebDeployments),
RefAttrs: map[string]*resourceExporter.RefAttrSettings{
"flow_id": {RefType: "genesyscloud_flow"},
"configuration.id": {RefType: "genesyscloud_webdeployments_configuration"},
},
ExcludedAttributes: []string{"configuration.version"},
}
}
func DataSourceWebDeploymentsDeployment() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud Web Deployments. Select a deployment by name.",
ReadContext: provider.ReadWithPooledClient(dataSourceDeploymentRead),
Schema: map[string]*schema.Schema{
"name": {
Description: "The name of the deployment",
Type: schema.TypeString,
Required: true,
},
},
}
}
package webdeployments_deployment
import (
"errors"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v129/platformclientv2"
)
func alwaysDifferent(k, old, new string, d *schema.ResourceData) bool {
return false
}
func validateDeploymentStatusChange(k, old, new string, d *schema.ResourceData) bool {
// Deployments will begin in a pending status and may or may not make it to active (or error) by the time we retrieve their state,
// so allow the status to change from pending to a less ephemeral status
return old == "Pending"
}
func flattenConfiguration(configuration *platformclientv2.Webdeploymentconfigurationversionentityref) []interface{} {
return []interface{}{map[string]interface{}{
"id": *configuration.Id,
"version": *configuration.Version,
}}
}
func validAllowedDomainsSettings(d *schema.ResourceData) error {
allowAllDomains := d.Get("allow_all_domains").(bool)
_, allowedDomainsSet := d.GetOk("allowed_domains")
if allowAllDomains && allowedDomainsSet {
return errors.New("Allowed domains cannot be specified when all domains are allowed")
}
if !allowAllDomains && !allowedDomainsSet {
return errors.New("Either allowed domains must be specified or all domains must be allowed")
}
return nil
}